# Flare Developer Hub > Official documentation for Flare. This file contains all documentation content in a single document following the llmstxt.org standard. ## Flare Developer Hub ☀️ Start building on Flare, the EVM-compatible Layer 1 built for data-intensive, interoperable applications. Flare Developer Hub is your central resource for tutorials, SDKs, and API docs. Build with Flare's enshrined data protocols, fast finality, and soon, verifiable compute with TEEs. ## Getting Started | Network | Flare Mainnet | Flare Testnet Coston2 | | :----------- | :------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | **RPC** | flare-api.flare.network/ext/C/rpc | coston2-api.flare.network/ext/C/rpc | | **Chain ID** | 14 | 114 | | **Explorer** | [`https://flare-explorer.flare.network`](https://flare-explorer.flare.network) | [`https://coston2-explorer.flare.network`](https://coston2-explorer.flare.network) | | **Faucet** | N/A | Request C2FLR, FXRP and USDT0 from the [Coston2 Faucet](https://faucet.flare.network/coston2) | Additional configuration and API resources are detailed on the [Network](/network/overview) page. ## Integrate Flare's protocols ## AI tools ## Ecosystem tools ## Understand the architecture Gain a deep understanding of the core concepts that differentiate Flare from other blockchains. Its native data protocols, [Flare Time Series Oracle](/ftso/overview) and [Flare Data Connector](/fdc/overview), are enshrined directly into the core [Flare Systems Protocol](/network/fsp), inheriting the full economic security of the network. ## Contribute to Flare ## Getting Support --- ## Network Flare provides four EVM-compatible, permissionless networks designed for different stages of application and protocol development: - **Flare Mainnet:** The production network, transactions cost FLR here. - **Flare Testnet Coston2:** Testnet for dApp development. - **Songbird Canary-Network:** Experimental network for protocol upgrades, transactions cost SGB here. - **Songbird Testnet Coston:** Testnet for protocol development. Choose your development track depending on your goals: 1. **Application Development** (Recommended for dApp developers): - Flare Testnet Coston2 → Flare Mainnet 2. **Protocol Development** (Required for all protocol-level changes): - Songbird Testnet Coston → Songbird Canary-Network → Flare Testnet Coston2 → Flare Mainnet ## Configuration Specific configuration details for connecting to each Flare network, including public RPC endpoints, chain identifiers, blockchain explorers, and testnet faucets. | Network name | Flare Mainnet | | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | **Public RPCs (HTTPS)** | `https://flare-api.flare.network/ext/C/rpc` `https://stylish-light-theorem.flare-mainnet.quiknode.pro/ext/bc/C/rpc` | | **Public RPCs (WSS)** | `wss://flare-api.flare.network/ext/C/ws` `wss://stylish-light-theorem.flare-mainnet.quiknode.pro/ext/bc/C/ws` | | **Chain ID** | `14` | | **Native currency** | `FLR` (18 decimals) | | **Block Explorer** | [`https://flare-explorer.flare.network`](https://flare-explorer.flare.network) | | **Systems Explorer** | [`https://flare-systems-explorer.flare.network`](https://flare-systems-explorer.flare.network) | | **Faucet** | - | | **Bootstrapping nodes** | `https://flare-bootstrap.flare.network` `https://flare-bootstrap-1.staking.production.figment.io` `https://flare.senseinode.com` | | **\{Safe\}Wallet** | [`https://multisig.flare.network`](https://multisig.flare.network) | :::tip[RPC Connection code snippet] ```bash curl https://flare-api.flare.network/ext/C/rpc -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' ``` {BlockNumFlareJS} {BlockNumFlarePy} {BlockNumFlareGo} {BlockNumFlareRs} ::: | Network name | Flare Testnet Coston2 | | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Public RPCs (HTTPS)** | `https://coston2-api.flare.network/ext/C/rpc` `https://falling-skilled-uranium.flare-coston2.quiknode.pro/ext/bc/C/rpc` | | **Public RPCs (WSS)** | `wss://coston2-api.flare.network/ext/C/ws` `wss://falling-skilled-uranium.flare-coston2.quiknode.pro/ext/bc/C/ws` | | **Chain ID** | `114` | | **Native currency** | `C2FLR` (18 decimals) | | **Block Explorer** | [`https://coston2-explorer.flare.network`](https://coston2-explorer.flare.network) | | **Systems Explorer** | [`https://coston2-systems-explorer.flare.network`](https://coston2-systems-explorer.flare.network) | | **Faucet** | Request C2FLR, FXRP, and USDT0 from [Coston2 Faucet](https://faucet.flare.network/coston2) | | **Bootstrapping nodes** | `https://coston2-bootstrap.flare.network` | :::tip[RPC Connection code snippet] ```bash curl https://coston2-api.flare.network/ext/C/rpc -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' ``` {BlockNumCoston2JS} {BlockNumCoston2Py} {BlockNumCoston2Go} {BlockNumCoston2Rs} ::: | Network name | Songbird Canary-Network | | :----------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Public RPC (HTTPS)** | `https://songbird-api.flare.network/ext/C/rpc` | | **Public RPC (WSS)** | `wss://songbird-api.flare.network/ext/C/ws` | | **Chain ID** | `19` | | **Native currency** | `SGB` (18 decimals) | | **Block Explorer** | [`https://songbird-explorer.flare.network`](https://songbird-explorer.flare.network) | | **Systems Explorer** | [`https://songbird-systems-explorer.flare.network`](https://songbird-systems-explorer.flare.network) | | **Faucet** | - | | **Bootstrapping nodes** | `https://songbird-bootstrap.flare.network` | :::tip[RPC Connection code snippet] ```bash curl https://songbird-api.flare.network/ext/C/rpc -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' ``` {BlockNumSongbirdJS} {BlockNumSongbirdPy} {BlockNumSongbirdGo} {BlockNumSongbirdRs} ::: | Network name | Songbird Testnet Coston | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Public RPC (HTTPS)** | `https://coston-api.flare.network/ext/C/rpc` | | **Public RPC (WSS)** | `wss://coston-api.flare.network/ext/C/ws` | | **Chain ID** | `16` | | **Native currency** | `CFLR` (18 decimals) | | **Block Explorer** | [`https://coston-explorer.flare.network`](https://coston-explorer.flare.network) | | **Systems Explorer** | [`https://coston-systems-explorer.flare.network`](https://coston-systems-explorer.flare.network) | | **Faucet** | Request CFLR from [Coston Faucet](https://faucet.flare.network/coston) | | **Bootstrapping nodes** | `https://coston-bootstrap.flare.network` | :::tip[RPC Connection code snippet] ```bash curl https://coston-api.flare.network/ext/C/rpc -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' ``` {BlockNumCostonJS} {BlockNumCostonPy} {BlockNumCostonGo} {BlockNumCostonRs} ::: ## API Resources Resources for interacting with Flare's DA Layer and FDC verifiers. | **DA Layer** | [`https://flr-data-availability.flare.network/api-doc`](https://flr-data-availability.flare.network/api-doc) | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | **FDC Verifier (EVM)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/api-doc) | | **FDC Verifier (BTC)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/btc/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/btc/api-doc) | | **FDC Verifier (XRP)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/xrp/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/xrp/api-doc) | | **FDC Verifier (DOGE)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/doge/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/doge/api-doc) | | **FDC Verifier (Web2Json)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/web2/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/web2/api-doc) | | **DA Layer** | [`https://ctn2-data-availability.flare.network/api-doc`](https://ctn2-data-availability.flare.network/api-doc) | | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | | **FDC Verifier (EVM)** | [`https://fdc-verifiers-testnet.flare.network/verifier/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/api-doc) | | **FDC Verifier (BTC)** | [`https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc) | | **FDC Verifier (XRP)** | [`https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc) | | **FDC Verifier (DOGE)** | [`https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc) | | **FDC Verifier (Web2Json)** | [`https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc) | | **DA Layer** | [`https://sgb-data-availability.flare.network/api-doc`](https://sgb-data-availability.flare.network/api-doc) | | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | | **FDC Verifier (EVM)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/api-doc) | | **FDC Verifier (BTC)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/btc/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/btc/api-doc) | | **FDC Verifier (XRP)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/xrp/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/xrp/api-doc) | | **FDC Verifier (DOGE)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/doge/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/doge/api-doc) | | **FDC Verifier (Web2Json)** | [`https://fdc-verifiers-mainnet.flare.network/verifier/web2/api-doc`](https://fdc-verifiers-mainnet.flare.network/verifier/web2/api-doc) | | **DA Layer** | [`https://ctn-data-availability.flare.network/api-doc`](https://ctn-data-availability.flare.network/api-doc) | | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | | **FDC Verifier (EVM)** | [`https://fdc-verifiers-testnet.flare.network/verifier/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/api-doc) | | **FDC Verifier (BTC)** | [`https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc) | | **FDC Verifier (XRP)** | [`https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc) | | **FDC Verifier (DOGE)** | [`https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc) | | **FDC Verifier (Web2Json)** | [`https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc`](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc) | **API Keys:** - Public Verifier API key: `00000000-0000-0000-0000-000000000000` - For higher rate limits on the DA Layer raise an [API Key Request](https://github.com/flare-foundation/developer-hub/issues/new/choose) issue on GitHub. ## Supported wallets A variety of wallets across desktop, browser extension, and mobile platforms support Flare. Discover suitable options for your needs on the [Flare Wallets](https://flare.network/wallets) page. ## Transaction format - **Address space:** Matches Ethereum, 20-byte addresses using ECDSA. - **Transaction format:** Matches Ethereum, complies with [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718), encoded with [RLP](https://ethereum.org/developers/docs/data-structures-and-encoding/rlp/). - **Transaction fees:** - **Type0** (Legacy) - Fee is calculated as `gasUsed * gasPrice`. - **Type2** (EIP-1559) - Fee is calculated as `(baseFee + priorityFee) * gas`. - In both cases, all transaction fees are burned. ## Smart contracts - **Compatibility:** Fully EVM-compatible. Contracts written in Solidity, Vyper, or other EVM languages can be deployed directly. - **RPC-API:** [Ethereum RPC API](https://ethereum.org/developers/docs/apis/json-rpc/) - **Supported opcodes:** Supports all EVM [opcodes](https://www.evm.codes/?fork=cancun) up to and including the Cancun hard fork. ## Consensus mechanism - **Consensus Protocol:** Snowman++ (from [Avalanche](https://build.avax.network/docs/primary-network)) provides a high-throughput, totally ordered consensus with fast finality. Learn more on the [Consensus](/network/consensus) page. - **Sybil resistance mechanism:** Proof-of-Stake (PoS) - **Delegation:** In-protocol - **Block time:** ≈1.8 seconds - **Finality:** Single-slot finality. Once a block is accepted through the consensus process (gossip), it's considered final. - **Transaction ordering:** Determined by the block proposer (leader), the default behavior is priority gas auction. - **Participants (Validators):** - Nodes must meet a [minimum self-bond requirement](https://proposals.flare.network/FIP/FIP_5.html) (defined by governance) to become validators. - Validators participate in consensus voting and are randomly selected as leaders to propose new blocks, weighted by their total stake (self-bond + delegated stake). - Validator count and stake distribution are viewable live on the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/validators). - **Enshrined protocols:** In addition to consensus, Flare validators are also data providers for [FTSO](/ftso/overview) and [FDC](/fdc/overview). ## Block verification - **Block Header Verification:** [Example Go implementation](https://github.com/flare-foundation/go-flare/blob/main/coreth/consensus/dummy/consensus.go). - **Block Body Validation:** [Example Go implementation](https://github.com/flare-foundation/go-flare/blob/main/coreth/core/block_validator.go). - **Verify transaction in block:** Verified using Merkle Patricia Trie proofs against the receipts root included in the block header. :::note[Adding Flare (FLR) to your exchange] Integrating Flare is similar to integrating Ethereum or other EVM chains. To add it to your exchange set up an [RPC node](/run-node#rpc-node) and use the appropriate [network configuration](#configuration) for Flare Mainnet. Additional resources: [Flare Brand Kit](https://flare.network/media), [go-flare source code](https://github.com/flare-foundation/go-flare) ::: ## Watch the video --- ## Getting Started You can deploy your first smart contract and run it in your browser without prior knowledge of Flare. This guide demonstrates how easy it is to develop smart contracts using the [Solidity language](https://www.soliditylang.org/), a [MetaMask wallet](https://metamask.io/) and the [Remix Development Environment](https://remix.ethereum.org/). All these tools are accessible in your browser for free, without requiring any sign-up. ## Goals You will create and deploy a simple "Hello World" smart contract following these steps: 1. **Write:** Draft a smart contract outlining its logic, updating a simple string state variable. 2. **Compile:** Convert your human-readable smart contract code into bytecode, comprehensible to the Flare blockchain. 3. **Deploy:** Send the compiled smart contract to the blockchain, where its code becomes immutable. 4. **Call functions:** Execute the functions defined in your contract, triggering changes in the state of the blockchain. ## Steps
**1. Install, configure and fund your MetaMask wallet** 1. [Install the MetaMask browser extension wallet](https://metamask.io/download/) 2. After installing, open MetaMask from your browser extensions. 3. Follow the instructions to create a new wallet. During setup, you'll receive a 12-word mnemonic phrase. Safeguard this phrase in a secure location, as it's crucial for accessing your wallet in the future. 4. Add the Flare Testnet Coston2 to your MetaMask wallet. Go to the [Coston2 Explorer](https://coston2-explorer.flare.network), and click on **Connect** in the top right hand corner. 5. A MetaMask prompt will open asking you to approve adding the network. Click on **Approve**. 6. Once approved, MetaMask will ask you to switch to Flare Testnet Coston2. Click on **Switch network**. 7. Copy your MetaMask address. 8. Paste your address in the [Coston2 Faucet](https://faucet.flare.network/coston2) and click on **Request C2FLR**. 9. After the faucet completes the transaction, which can take a few seconds, you should find testnet C2FLR in your MetaMask wallet. With your wallet configured and funded, you're ready to write, compile, and deploy your contract.
**2. Write, compile and deploy your first smart contract** Start with a simple HelloWorld.sol example. This contract illustrates setting and retrieving variables within a smart contract onchain. {HelloWorld}
Didn't understand the Solidity code? Let's break down the `HelloWorld` contract: 1. **Pragma Directive:** The `pragma solidity >=0.8.0 <0.9.0;` statement specifies the version of the Solidity compiler the contract should use. In this case, the contract is compatible with any version of Solidity above (including) 0.8.0 and below 0.9.0. 2. **Contract Declaration:** The `contract HelloWorld { ... }` statement defines a new Solidity contract named `HelloWorld`. 3. **State Variable:** `string public message`; declares a state variable named `message`, which is of type `string` and is publicly accessible (due to the `public` visibility modifier). This variable will store a message that can be read by any external entity. 4. **Constructor:** The `constructor(string memory initialMessage) { ... }` function is a special function that is executed only once when the contract is deployed. It initializes the `message` state variable with the value passed as `initialMessage` when the contract is deployed. 5. **Function `updateMessage`:** This function allows anyone to update the `message` state variable. It takes a `newMessage` parameter of type `string`, updates the `message` variable with the new value, and is publicly accessible (`public` visibility modifier).
{/* prettier-ignore */} 1. Open contract in Remix 2. Click on `HelloWorld.sol` in the file explorer to open the contract in the Remix editor. 3. Navigate to the **Solidity compiler** tab on the left to view the compiler settings. 4. Expand the **Advanced Configurations** section and make sure the **EVM Version** is set to `cancun`. 4. Click the **Compile HelloWorld.sol** button to compile the contract. This converts the contract from human-readable Solidity code into bytecode that the Flare blockchain can understand. 5. After Remix compiles the contract, deploy it. On the left side of Remix, click the **Deploy & Run Transactions** tab to view the deployment settings. 6. In the deployment settings, select the **Injected Provider - MetaMask** environment. This tells Remix that you want to deploy your contract to the blockchain that you configured in MetaMask. 7. Next to the **Deploy** button, enter a message that you want to send with the smart contract when you deploy it. This contract has a constructor that sets an initial message when you deploy the contract. 8. Click the **Deploy** button to deploy the contract and its initial message to the blockchain. MetaMask opens and asks you to confirm payment to deploy the contract. Make sure MetaMask is set to the Flare Testnet Coston2 network before you accept the transaction. Click on **Confirm**. 9. After a few seconds, the transaction completes and your contract appears under the **Deployed/Unpinned Contracts** list in Remix. Click the contract dropdown to view its variables and functions. Click the **message** button. Remix retrieves and prints the initial message that you set. The contract has an address just like your wallet address. To see details about your deployed contract, copy the contract address from the list in Remix and search for it in the [Coston2 Explorer](https://coston2-explorer.flare.network).
**3. Call functions in your contract** Since you deployed the contract to a blockchain, multiple nodes on the test network have confirmed your payment for the smart contract. The contract, along with its variables and functions, is now permanently stored on the blockchain. To change the `message` variable within your contract, simply run the `updateMessage` function. 1. In your deployed contract, enter a new message next to the updateMessage function. Click the **updateMessage** button to set the new message in the contract data. 2. A MetaMask prompt will open and ask you to confirm payment to update the state of your contract. Click **Confirm** to approve the transaction. 3. Click the **message** button again to see the updated value.
Now you know how to deploy and call example contracts on Flare's testnet. You can write your own contracts and test them using this same process. ## Watch the video :::tip[What's next?] Read FTSOv2's [Getting Started](/ftso/getting-started) guide to learn how to connect your smart contracts to Flare's enshrined oracle and retrieve onchain data feeds. ::: --- ## Developer Tools ## Bridges ## RPCs ## OFTs ## Indexers ## Wallet SDKs ## Full-stack infra ## Analytics ## Explorers --- ## Governance Governance is the structured process for making decisions about the Flare and Songbird networks. It empowers the community to guide the evolution of the protocols, playing a crucial role in progressive decentralization. Through this framework, participants can: - Propose changes and improvements. - Vote on submitted proposals. - Observe the execution of approved proposals. Official proposals are published on the [Flare Governance Proposals](https://proposals.flare.network/) website. Community members cast their votes via the [Flare Portal](https://portal.flare.network/). ## Proposal types and networks Three distinct proposal types facilitate network changes, each tailored to a specific network and purpose, with unique voting mechanics: ### Flare Improvement Proposals (FIPs) - **Purpose:** Introduce enhancements or changes specifically for the Flare Mainnet. - **Network**: Flare Mainnet - **Initiator**: Flare Foundation - **Voting Tokens**: `WFLR` (wrapped `FLR`) and staked `FLR` - **Voting Mechanism:** Acceptance-based. - **Default Status:** Rejected - **Approval Condition:** Requires a simple majority (more than 50%) of the cast votes to be in favor. - **Quorum Requirement:** None ### Songbird Test Proposals (STPs) - **Purpose:** Allow for rapid testing and validation of potential changes on Songbird Canary-Network _before_ potentially proposing them (as FIPs) on Flare Mainnet. - **Network:** Songbird Canary Network - **Initiator:** Flare Foundation - **Voting Tokens:** Wrapped SGB (`WSGB`) - **Voting Mechanism:** Rejection-based. - **Default Status:** Accepted - **Rejection Conditions:** The proposal is rejected _only if both_ of the following conditions are met simultaneously: 1. **Quorum Threshold:** At least 75% of the total circulating `SGB` supply participates in the vote. 2. **Majority Against:** More than 50% of the cast votes are against the proposal. - **Outcome:** If either the quorum is not met OR the vote against is not a majority, the STP is accepted. ### Songbird Improvement Proposals (SIPs) - **Purpose:** Introduce enhancements or changes specifically for the Songbird Canary Network, independent of Flare Mainnet testing. - **Network:** Songbird Canary Network - **Initiator:** Flare Foundation - **Voting Tokens:** Wrapped SGB (`WSGB`) - **Voting Mechanism:** Acceptance-based. - **Default Status:** Rejected - **Approval Condition:** Requires a simple majority (more than 50%) of the cast votes to be in favor. - **Quorum Requirement:** None ## Voting process ### Eligibility and Vote Power To vote on a proposal, users must hold the relevant network's governance tokens at the time the **Vote Count Block** is determined: - **FIPs (Flare):** Requires holding `WFLR` or having staked `FLR`. - **STPs & SIPs (Songbird):** Requires holding `WSGB`. Your **Vote Power** corresponds directly to the amount of eligible tokens held at the snapshot time (Vote Count Block). The Flare Foundation announces proposals well in advance, providing token holders ample time to wrap their `FLR` or `SGB` if they wish to participate in upcoming votes. ### Vote Count Block Since token balances change constantly, a specific block number is chosen before voting commences to serve as a snapshot of account balances. This **Vote Count Block** determines each participant's voting power for that specific proposal. To prevent users from acquiring tokens solely to influence a vote just before it starts, the Vote Count Block is selected randomly within a predefined period after the proposal's announcement. #### Vote transfer Users can transfer their voting power to another address without transferring ownership of their tokens. This is useful for consolidating voting power from multiple wallets into one voting address. - An address can transfer its votes to only one other address. - An address can receive transferred votes from multiple addresses. - Transferred votes cannot be further re-transferred. - Once activated, all current and future voting power from the delegating account is transferred to the designated address until the delegation is explicitly canceled. **Example:** If Alice transfers her votes while holding 100 `WSGB`, her designated address receives 100 votes. If she later acquires another 100 `WSGB`, her designated address will automatically have 200 votes in the next proposal, provided the transfer remains active. ### Voting procedure Each governance proposal follows a structured lifecycle: 1. **Proposal Announcement:** The Flare Foundation publishes the detailed proposal on the [Flare Governance Proposals](https://proposals.flare.network/) site, the main Flare website, and official social media channels. 2. **Notice Period:** A minimum period (typically one reward epoch) allows the community to discuss the proposal, ask questions, and identify potential issues. Flawed proposals might be cancelled during this time. 3. **Block Selection Period:** A window during which the random Vote Count Block is determined. 4. **Voting Period:** Token holders cast their votes on the [Flare Portal](https://portal.flare.network/). This period usually lasts one week (approximately two reward epochs). ## Proposal execution Once a proposal is formally accepted through the voting process, its implementation depends on the nature of the required changes: - **Smart Contract Execution:** Changes implemented directly within protocol smart contracts can often be executed automatically via governance contract calls. - **Manual Execution:** Proposals requiring offchain actions, coordination, or complex system updates are typically executed by the Flare Foundation. - **Future Automation:** The system is designed to incorporate more onchain automation for execution as the protocols evolve. ## Management Group Introduced via [FIP.02](https://proposals.flare.network/FIP/FIP_2.html) (Flare) and [STP.03](https://proposals.flare.network/STP/STP_3.html) (Songbird), the **Management Group** is a specialized governance body composed of eligible FTSO data providers. **Initial Role:** - Monitor and report instances of **collusion** among Flare Time Series Oracle (FTSO) data providers. - Vote collectively on punitive actions (like **chilling** or **banning**) against providers confirmed to be acting maliciously. **Expanded Responsibilities (Post-FIP.08, FIP.11, FIP.12):** Following subsequent approved proposals, the Management Group's mandate grew to include voting on: - Adding new data feeds to the [FTSO protocol](/ftso/overview). - Adding new attestation types to the [FDC protocol](/fdc/overview). - Adjusting specific protocol parameters as defined in [FIP.11](https://proposals.flare.network/FIP/FIP_11.html) and [FIP.12](https://proposals.flare.network/FIP/FIP_12.html). **Qualification:** To be part of the Management Group, data providers must be active participants in the core protocols, eligible for rewards, and have a clean recent conduct record (i.e., not recently punished). Together, these responsibilities position the Management Group as a key governance body within the ecosystem - responsible not only for safeguarding protocol integrity but also for guiding its ongoing evolution. --- ## Consensus The consensus protocol running on all Flare networks is Snowman++, introduced by [Avalanche](https://build.avax.network/docs/primary-network). This runs on both the P- and C-chains. Snowman++ is a Byzantine fault tolerant (BFT) protocol, being part of the wider Snow family of probabilistic protocols, which includes: - **Slush:** the foundational protocol in the Snow family. It operates in iterative rounds, during which each node samples a subset of the network and updates its local state according to the sampled majority. Nodes terminate simply once a predefined number of rounds has elapsed. Slush by itself is not Byzantine fault tolerant. - **Snowflake:** an extension of Slush which introduces an additional threshold parameter for finalization. As a result, a node finalizes a state only after a specified number of consecutive queries consistently supports that same state. This refinement enhances Slush by improving its finalization properties. - **Snowball:** augments Snowflake by incorporating local confidence counters. Each time a query reaffirms a particular state, the corresponding confidence counter for that state increments. This provides “memory” to the protocol, allowing nodes to accumulate additional certainty in their decisions over time. - **Snowman:** while Avalanche originally relied on a directed acyclic graph (DAG) structure, Snowman is the linear-chain version of it. Within Snowman there are multiple Snowball instances running concurrently at various block heights, each awaiting finalization. Snowman's key advantage is its ability to leverage a single set of correspondences to drive several simultaneous Snowball processes, thereby enhancing the overall efficiency of the consensus mechanism. - **Snowman++:** introduces a soft proposer mechanism, initially granting a single proposer the authority to create a block. Over time, additional validators become eligible to propose blocks, with each validator selected according to its stake. This design further refines Snowman's block-production process by incorporating stake-weighted proposer selection. ## Slush **Slush** serves as the foundational protocol in the Snow family. In Slush, a node repeatedly samples the network and queries each selected peer regarding its current preferred state. The protocol is governed by two parameters: - `K`: the number of nodes included in each sample. - `Alpha`: the minimum threshold—constrained such that `K/2 < Alpha < K + 1`—required for a query to be considered successful. That is, at least `Alpha` nodes must support the same state. When a query is deemed successful - i.e., it garners at least `Alpha` votes in favor of a particular state -- the node updates its own preference to match the majority state of the sample. The original formulation of Slush was defined for a binary decision scenario. The image below depicts such a binary decision example, where a user samples the network, queries the sampled participants, and updates its preferred state according to the majority. The example uses `K=5` and `Alpha=3` for the protocol parameters. Slush does not incorporate inherent fault tolerance. This limitation is evident in its termination process, as the algorithm executes for a predetermined number of rounds rather than terminating based on reaching a consensus resilient against Byzantine faults. The Slush algorithm is implemented as part of the [go-flare](https://github.com/flare-foundation/go-flare) client, within the [snow/consensus/snowball](https://github.com/flare-foundation/go-flare/tree/main/avalanchego/snow/consensus/snowball) package. The global parameters are defined as part of the [genesis configuration](https://github.com/flare-foundation/go-flare/tree/main/avalanchego/genesis). ## Snowball **Snowflake** and **Snowball** are enhancements to the original Slush protocol, introducing additional global and local parameters to govern when a node may finalize its decision. - `Beta`: the required number of consecutive successful queries that support the same outcome before a node can finalize. In other words, a node is deemed to be finalized once it achieves the same decision in `Beta` consecutive successful rounds. This represents a significant improvement over Slush, which lacked any dynamic security mechanism within its fixed termination conditions. - Confidence counter: Snowball adds to this logic an additional local confidence counter, `confidence`, which effectively further extends the protocol memory. This counter tracks the number of consecutive successful polls that have returned the current preference. A node will only update its local **preference** if the counter for a new state exceeds that of the current preference. ### `Tree` structure In the Go implementation from the [snow/consensus/snowball](https://github.com/flare-foundation/go-flare/tree/main/avalanchego/snow/consensus/snowball) package, the Snowflake and Snowball protocols share a common interface. For both protocols, decisions are made on the 32-byte binary form of the proposed blocks. To effectively deal with more than two choices at a time, Snowball implements a `Tree` structure for all concurrent decisions. This implementation is specialized to handle multi-branch conflicts elegantly, by modeling them as a hierarchy, and thus optimizing the algorithm by allowing pruning. More precisely, concurrent blocks at the same height are organized in a tree-like structure based on the first differing bit. As such, when adding a new block, the tree branches out through a `binaryNode` at the first differing bit, with the counting done from the most significant bit to the least significant bit. Here, bit indices are defined as: ```plaintext [7 6 5 4 3 2 1 0] [15 14 13 12 11 10 9 8] ... [255 254 253 252 251 250 249 248] ``` An example of how concurrent blocks may be organized within the `Tree` structure is depicted below, where the first differing bit in a byte is also highlighted. In the `Tree` implementation, each node in the tree structure holds a `Preference` and a `confidence` counter. Note that if the counter of a `binaryNode` increases following a successful poll, this propagates further up the tree towards the root node. `binaryNodes` can finalize only when the confidence counter reaches the globally defined `Beta` value, upon which they are replaced by `unaryNodes`, thus leading to branch trimming. A key aspect of this implementation is that it avoids running polls for every single bit in the 32-byte representation at each block height. Next up we will see how Snowman optimizes the consensus algorithm further, by implementing a graph-like structure for blocks at different heights. ## Snowman While Snowball is used for deciding between concurrent blocks at the same block height, **Snowman** wraps this logic and extends it to a full linear blockchain. Snowman does enforce linearity, but non-finalized blocks can be temporarily organized into a DAG structure, which could simply be due to network delays for example. To convert this DAG structure to a linear graph, Snowman uses **Kahn's topological ordering algorithm**. Naturally, for every block height, a set of block proposers is chosen in a deterministic fashion, as we discuss momentarily. This set is randomly determined using a seed based on the block height and on a characteristic that is chain-specific. Khan's algorithm is used to organize blocks into a linear chain by enforcing the correct order of processing. More precisely, the algorithm enforces the parent-child relationships within the chain. - **Example:** A validator might receive block `B` (at height `N`) before block `A` (at height `N-1`). Khan's algorithm ensures that block `B` is processed only after block `A` is available and processed. Khan's topological ordering algorithm ensures that: - A block should not be added twice. - A block that is being added should never have a child that was already added. ### Go Implementation The Snowman consensus is implemented through a `Topological` struct, which uses a graph for tracking the strongly preferred branch. Within this structure, topological order is done from leaves towards the genesis block. For each round, validators report their currently preferred chain, rather than a single bit value, or a single block. Votes are then propagated transitively towards the genesis block, respecting parent-child relationships: a vote cast for a leaf implicitly supports its parent, provided that parent has not already been finalized. In the diagram below, blocks that have not been finalized are shown with dashed borders. A vote cast for block `G` is then propagated upward to blocks `F` and `C`. Notably, a separate Snowball instance is running for every parent node within this structure. For example, at block `C`, the three concurrent child blocks are organized using the previously discussed Snowball `Tree` structure. Consequently, the primary advantage of Snowman is its ability to leverage a single set of correspondences to drive multiple simultaneous instances of Snowball, thereby achieving efficiency and coherence across the consensus process. ### Validator Sampling A Snowman chain is initialized within the `Manager` interface of the [chains](https://github.com/flare-foundation/go-flare/tree/main/avalanchego/chains) package. This configures the global consensus parameters, as defined in the genesis files, as well as the type of sampling used by the validators. The widely used sampling process in the consensus protocol is a weighted sampling without replacement, which is enforced in the [snow/validators](https://github.com/flare-foundation/go-flare/tree/main/avalanchego/snow/validators) package. Here the weights are based on the validators' stake. ## Snowman++ Snowman++ introduces a soft proposer mechanism, with a single proposer with the power to issue a block initially. The proposer selection is deterministic, based on the validator set and the chain height. Validators are selected using a weighted sampling without replacement, with the weight system based on stake. At each block height, a fixed number of validators is selected, given by the protocol parameter `maxWindows`. Based on the position in the sampled set, the selected validators are then assigned a delay dictated by `WindowDuration`: the amount of time the validator must wait before being eligible to propose a block. These two parameters are part of the `proposerVM`, being configured as follows: - `maxWindows`: currently set to 6. - `WindowDuration`: currently set at 5 seconds. --- ## Network Reference ## Deployed Contracts export const contracts = [ "FlareContractRegistry", "ProtocolsV2", "RandomNumberV2", "RewardsV2", "ClaimSetupManager", "DistributionToDelegators", "WNat", "RNat", ]; :::tip `FlareContractRegistry` has the same address across all four networks. You can query the [`getAllContracts`](/network/solidity-reference/IFlareContractRegistry#getallcontracts) method to fetch all protocol contract addresses on that network. ::: ## Interfaces --- ## Flare Systems Protocol The **F**lare **S**ystems **P**rotocol (**FSP**) is a foundational infrastructure designed to support Flare's enshrined protocols (technically referred to as sub-protocols). Its primary goal is to facilitate secure, efficient, and decentralized consensus mechanisms through weighted voting by a select group of entities known as **data providers** or **voters**. These data providers are offchain participants who accrue vote power from the Flare community via delegations of wrapped FLR tokens (WFLR) or stakes. FSP ensures that agreements on offchain data or calculations are reached securely and fairly, enabling the reliable operation of sub-protocols like the [Flare Time Series Oracle](/ftso/overview) and the [Flare Data Connector](/fdc/overview). **Key FSP Features:** - **Decentralized Governance**: Through a weighted voting system involving a diverse set of voters. - **Efficient Data Management**: By offloading complex calculations offchain and minimizing onchain storage requirements. - **Robust Reward Mechanisms**: Incentivizing participation and penalizing delays or non-compliance to maintain network health. - **Extensibility**: Designed to support additional sub-protocols and future enhancements like C-chain staking. - **Security**: Implements mechanisms to prevent malicious behavior and ensures data integrity through Merkle proofs. ## Watch the video ## Components --- ## Flare Transaction SDK The Flare Transaction SDK ([`@flarenetwork/flare-tx-sdk`](https://www.npmjs.com/package/@flarenetwork/flare-tx-sdk/)) is the official Node.js toolkit for performing common actions on all Flare networks - Flare, Coston2, Songbird, and Coston. - [Claim rewards](/network/flare-tx-sdk/cookbook#claim-rewards) (FlareDrops, staking, FTSO delegation, rFLR) - [Delegate to FTSO providers](/network/flare-tx-sdk/cookbook#delegate-to-ftso-providers) - [Stake on the P-chain](/network/flare-tx-sdk/cookbook#stake-on-p-chain) - [Vote on governance proposals](/network/flare-tx-sdk/cookbook#vote-on-governance-proposals) - [Retrieve account and balance information](/network/flare-tx-sdk/cookbook#retrieve-account-balances) - [Transfer native and wrapped coins](/network/flare-tx-sdk/cookbook#wrap-and-transfer) - [Interact with C-Chain contracts](/network/flare-tx-sdk/cookbook#interact-with-c-chain-contracts) Explore the following guides to get started: :::tip[Need help or found a bug?] Open an issue on the [flare-foundation/flare-tx-sdk](https://github.com/flare-foundation/flare-tx-sdk) GitHub repository. ::: --- ## Getting Started(Flare-tx-sdk) This guide walks you through installation, core concepts, and a quick start example to fetch your first balance. ## Prerequisites Before you start, ensure you have the following: - [Node.js](https://nodejs.org/en/download) v18+ (LTS recommended) and a package manager (npm, yarn, or pnpm) - A compatible wallet: - **EIP-1193 wallets** (e.g. MetaMask, WalletConnect) - **Hardware wallets** (Ledger, Trezor). ## Installation Add the SDK to your project: ```bash npm install @flarenetwork/flare-tx-sdk ``` ```bash yarn add @flarenetwork/flare-tx-sdk ``` ```bash pnpm add @flarenetwork/flare-tx-sdk ``` ## Core concepts The SDK separates network logic from account management. This ensures the same wallet can be used across multiple networks without code changes. - **`Network`**: Represents the blockchain network you want to connect to. Available networks include: - `Network.FLARE` - `Network.COSTON2` - `Network.SONGBIRD` - `Network.COSTON` The `Network` object provides methods to query balances, transfer tokens, claim rewards, and interact with contracts. It uses Flare's [public RPCs](/network/overview#configuration) by default. - **`Wallet`**: Represents a user account. The SDK does not store private keys. Instead, it defines a [`Wallet`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/wallet/index.ts) interface which can be implemented by: - MetaMask and other browser wallets - `EIP1193WalletController` - Ledger - `LedgerWalletController` - Trezor - `TrezorWalletController` - Your own custom signer ## Example implementation The following example shows how to connect to Flare Mainnet using MetaMask (EIP-1193) and fetch an account's balances. ```javascript async function main() { console.log("Connecting to the Flare Mainnet..."); // 1. Initialize the Network object for Flare Mainnet const network = Network.FLARE; // 2. Connect to the active wallet in MetaMask const controller = new EIP1193WalletController(window.ethereum); const wallet = await controller.getActiveWallet(); // 3. Get the public key from the wallet const publicKey = await wallet.getPublicKey(); // 4. Derive the C-Chain and P-Chain addresses const cAddress = network.getCAddress(publicKey); const pAddress = network.getPAddress(publicKey); console.log(`C-Chain Address: ${cAddress}, P-Chain Address: ${pAddress}`); // 5. Fetch the account's complete balance overview console.log("Fetching balance..."); const balance = await network.getBalance(publicKey); console.log("✅ Balance retrieved successfully!"); console.log(JSON.stringify(balance, null, 2)); } main().catch(console.error); ``` Example output: ```json { "availableOnC": "1000000000000000000", "wrappedOnC": "500000000000000000", "availableOnP": "2000000000000000000", "stakedOnP": "1000000000000000000" } ``` :::info[Units] The SDK uses wei, the smallest unit of FLR (1 FLR = $10^{18}$ wei). To convert to wei: ```javascript const natsAmount = Amount.nats(10); // convert 10 FLR to wei const wNatsAmount = Amount.wnats(10); // convert 10 WFLR to wei ``` ::: ## Wallet controllers Wallet controllers adapt third-party wallets into the SDK's [`Wallet`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/wallet/index.ts) interface. They handle signing and address derivation, while your keys remain securely stored in the original wallet. This guide shows how to connect EIP-1193 wallets (e.g., MetaMask/WalletConnect) and hardware wallets (Ledger and Trezor). ### EIP-1193 (MetaMask, WalletConnect) Use the [`EIP1193WalletController`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/wallet/eip1193/controller.ts) with any browser wallet that exposes an EIP-1193 provider (e.g., `window.ethereum`). :::tip[Security] Your private keys **never** leave your wallet. The SDK only communicates with the wallet for signing. ::: ```javascript async function connectEip1193() { // Ensure a provider is available if (!window.ethereum) { throw new Error( "No EIP-1193 provider found. Install MetaMask or use WalletConnect.", ); } // 1. Ask wallet for permission to access accounts await window.ethereum.request?.({ method: "eth_requestAccounts" }); // 2. Create controller and get the active account as an SDK Wallet const controller = new EIP1193WalletController(window.ethereum); const wallet = await controller.getActiveWallet(); // 3. Derive addresses and fetch balance const network = Network.FLARE; const publicKey = await wallet.getPublicKey(); const cAddress = network.getCAddress(publicKey); const pAddress = network.getPAddress(publicKey); const balance = await network.getBalance(publicKey); console.log({ cAddress, pAddress, balance }); return { controller, wallet, network }; } ``` ### Ledger Use the [`LedgerWalletController`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/wallet/ledger/controller.ts). It supports either Zondax Flare App ([`@zondax/ledger-flare`](https://www.npmjs.com/package/@zondax/ledger-flare)) or Ledger ETH App ([`@ledgerhq/hw-app-eth`](https://www.npmjs.com/package/@ledgerhq/hw-app-eth)). :::tip[Security] Your private keys **never** leave your Ledger device. The SDK only communicates with the device for signing. ::: 1. **Installation:** ```bash npm i @flarenetwork/flare-tx-sdk @zondax/ledger-flare ``` ```bash npm i @flarenetwork/flare-tx-sdk @ledgerhq/hw-app-eth ``` 2. **Connect and derive a wallet:** ```javascript async function connectLedgerViaZondax() { const flrApp = FlareApp(...) // add transport here const controller = new LedgerWalletController(flrApp, null); const path = "m/44'/60'/0'/0/0"; const wallet = await controller.getWallet(path); const network = Network.FLARE; const pubKey = await wallet.getPublicKey(); console.log("Ledger public key:", pubKey); return { controller, wallet, network }; } ``` ```javascript async function connectLedgerViaEthApp() { // add Ledger transports here // see https://developers.ledger.com/docs/device-interaction/integration/how_to/transports const ethApp = Eth(...) const controller = new LedgerWalletController(null, ethApp); const path = "m/44'/60'/0'/0/0"; const wallet = await controller.getWallet(path); const network = Network.FLARE; const pubKey = await wallet.getPublicKey(); console.log("Ledger public key:", pubKey); return { controller, wallet, network }; } ``` ### Trezor Use the [`TrezorWalletController`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/wallet/trezor/controller.ts) with [Trezor Connect](https://connect.trezor.io/9/readme/connect/). :::tip[Security] Your private keys **never** leave your Trezor device. The SDK only communicates with the device for signing. ::: 1. **Installation:** ```bash npm i @flarenetwork/flare-tx-sdk @trezor/connect ``` 2. **Connect and derive a wallet:** ```javascript async function connectTrezor() { // add Trezor manifest // see https://connect.trezor.io/9/methods/other/init/ TrezorConnect.init(...) const controller = new TrezorWalletController(TrezorConnect); const path = "m/44'/60'/0'/0/0"; const wallet = await controller.getWallet(path); const network = Network.FLARE; const pubKey = await wallet.getPublicKey(); console.log("Trezor public key:", pubKey); return { controller, wallet, network }; } ``` :::info[Advanced] If none of the Wallet controllers listed here meet your needs, you can implement a [custom wallet controller](/network/flare-tx-sdk/advanced-usage#custom-wallets). ::: ## Next steps - Explore the [Cookbook](/network/flare-tx-sdk/cookbook) for task-oriented guides (balances, transfers, rewards, staking). - Check the [Advanced Features](/network/flare-tx-sdk/advanced-usage) for transaction callbacks and custom wallets. - Read the SDK source code on [GitHub](https://github.com/flare-foundation/flare-tx-sdk). :::tip[Need help or found a bug?] Open an issue on the [flare-foundation/flare-tx-sdk](https://github.com/flare-foundation/flare-tx-sdk) GitHub repository. ::: --- ## Cookbook This Cookbook contains ready-to-use code snippets for common operations with the Flare Transaction SDK. ## Retrieve account balances The Flare network uses two chains: - **C-Chain** (Contract Chain, EVM-compatible, smart contracts) - **P-Chain** (Platform Chain, staking & validators) Both derive addresses from the same public key. ```javascript // Get the public key from your wallet const publicKey = await wallet.getPublicKey(); // Derive the addresses for each chain const cAddress = network.getCAddress(publicKey); const pAddress = network.getPAddress(publicKey); // Get a full balance overview const balance = await network.getBalance(publicKey); // Or, query for specific balances const cBalance = await network.getBalanceOnC(cAddress); const cWrappedBalance = await network.getBalanceWrappedOnC(publicKeyOrCAddress); const pBalance = await network.getBalanceOnP(publicKey); const stakedBalance = await network.getBalanceStakedOnP(publicKey); ``` :::info The balances are returned in wei, the smallest unit of FLR (1 FLR = $10^{18}$ wei). ::: ## Wrap and transfer Transfer native FLR and its wrapped form (WFLR) on the C-Chain. Wrapping is required for governance and FTSO delegation. - **FLR** (native token) lives on both C and P-Chain. - **WFLR** (wrapped FLR) exists only on the C-Chain for use in smart contracts, governance and delegation. ```javascript // Transfer 1 FLR await network.transferNative(wallet, recipientAddress, Amount.nats(1)); // Wrap 1 FLR -> 1 WFLR await network.wrapNative(wallet, Amount.nats(1)); // Transfer 1 WFLR await network.transferWrapped(wallet, recipientAddress, Amount.wnats(1)); // Unwrap 1 WFLR -> 1 FLR await network.unwrapToNative(wallet, Amount.wnats(1)); ``` ## Claim rewards Flare supports multiple reward sources: FlareDrops, staking rewards, rNat (e.g. rFLR), and FTSO rewards. ### FlareDrops :::warning[FlareDrops have ended] FlareDrops concluded on January 30, 2026, completing the 36-month distribution schedule defined under [FIP.01](https://proposals.flare.network/FIP/FIP_1.html). WFLR, rFLR, and staked FLR no longer accrue the FlareDrop reward component. However, **protocol-level rewards remain unchanged**: you continue to earn rewards for FTSO delegation, FLR staking, and FAssets agent participation. Learn more about [FLR's operational utility era](https://flare.network/news/beyond-flaredrops-flr-enters-operational-utility-era). ::: [FlareDrops](https://flare.network/news/flaredrop-guide) are rewards periodically distributed to token holders. ```javascript // Check and claim FlareDrop rewards const flareDropAmount = await network.getClaimableFlareDropReward(cAddress); if (flareDropAmount > 0) { await network.claimFlareDropReward(wallet); } ``` Optional parameters: | **Field** | **Type** | **Description** | | ------------- | --------- | ------------------------------------------ | | `rewardOwner` | `address` | C-Chain address of the reward owner | | `recipient` | `address` | C-Chain address to receive the reward | | `wrap` | `bool` | `true` for WFLR, `false` for FLR (default) | ### Staking rewards Earned from delegating tokens to validators on the P-Chain. ```javascript // Check and claim staking rewards const stakingRewardAmount = await network.getClaimableStakingReward(cAddress); if (stakingRewardAmount > 0) { await network.claimStakingReward(wallet); } ``` Optional parameters are the same as [FlareDrops](#flaredrops). ### rFLR rewards rNat tokens (rFLR on Flare) represent vested rewards. They are backed by WFLR in a dedicated rNat account. The flow of rFLR: ```plaintext rFLR (reward token) → backed by WFLR → claimable → withdraw → optional penalty if locked. ``` #### List and claim ```javascript // Get a list of all rNat projects let projects = await network.getRNatProjects(); ``` Returns an array of objects of type [`RNatProject`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/network/iotype.ts) with: | **Field** | **Type** | **Description** | | ------------------ | -------- | ----------------------------------------------------- | | `id` | `number` | Project ID | | `name` | `string` | Project name | | `claimingDisabled` | `bool` | Whether claiming rewards is disabled for this project | Claim rewards for one or more projects: ```javascript await network.claimRNatReward(wallet, [projectId1, projectId2]); ``` Claimed rewards are deposited as wrapped coins to the rNat account linked to the wallet's C-Chain address. #### Manage balances Check and claim the unlocked balance: ```javascript // Check the unlocked balance: const unlocked = await network.getUnlockedBalanceWrappedOnRNatAccount(cAddress); // Withdraw unlocked wrapped coins: await network.withdrawFromRNatAccount(wallet); ``` Get the full balance: ```javascript let balance = await network.getRNatAccountBalance(cAddress); ``` The returned [`RNatAccountBalance`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/network/iotype.ts) object includes: | **Field** | **Type** | **Description** | | --------------- | -------- | --------------------------------- | | `wNatBalance` | `bigint` | Wrapped coin balance (wei) | | `rNatBalance` | `bigint` | rNat token balance (wei) | | `lockedBalance` | `bigint` | Locked wrapped coin balance (wei) | :::warning[Withdrawing all funds] You can also withdraw all funds (locked + unlocked): ```javascript await network.withdrawAllFromRNatAccount(wallet, /* wrap: boolean */ true); ``` Withdrawing locked balance incurs a **penalty**, so the withdrawn amount will be reduced. ::: #### FlareDrops and rFLR The balance of rNat tokens equals the difference between rewards received and withdrawn. Note that the wrapped coins in an rNat account can exceed the rNat token balance. For example, wrapped coins can grow by claiming FlareDrop rewards earned from vested balances. Check unclaimed FlareDrop rewards: ```javascript let rNatAccountAddress = await network.getRNatAccount(cAddress); let amount = await network.getClaimableFlareDropReward(rNatAccountAddress); ``` Claim them: ```javascript await network.claimFlareDropReward( wallet, rNatAccountAddress, rNatAccountAddress, true, ); ``` ### FTSO delegation rewards Rewards earned by delegating WFLR to FTSO providers. ```javascript // Check and claim FTSO delegation rewards const ftsoRewardAmount = await network.getClaimableFtsoReward(cAddress); if (ftsoRewardAmount > 0) { await network.claimFtsoReward(wallet); } ``` Optional parameters: | **Field** | **Type** | **Description** | | ------------- | --------- | --------------------------------------------------------- | | `rewardOwner` | `address` | C-Chain address of the reward owner | | `recipient` | `address` | C-Chain address of where the reward should be transferred | | `wrap` | `bool` | Transfer reward as native (default) or wrapped | #### Claim with proofs If a reward isn't initialized, you must provide a Merkle proof available in the [flare-foundation/fsp-rewards](https://github.com/flare-foundation/fsp-rewards/) repository. As an example for reward epoch 320, see the proofs in [`reward-distribution-data.json`](https://github.com/flare-foundation/fsp-rewards/blob/main/flare/320/reward-distribution-data.json). ```javascript await network.claimFtsoReward(wallet, rewardOwner, recipient, wrap, proofs); ``` `proofs` is an array of [`FtsoRewardClaimWithProof`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/network/iotype.ts): | **Field** | **Type** | **Description** | | ------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------- | | `merkleProof` | `string[]` | The Merkle proof in hexadecimal encoding. | | `body` | [`FtsoRewardClaim`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/network/iotype.ts) | Specifying the reward claim details | ## Delegate to FTSO providers Delegating WFLR to FTSO providers allows you to earn rewards while contributing to decentralized price feeds. You may delegate to one or two providers. ```javascript // Get current delegations const delegations = await network.getFtsoDelegatesOf(cAddress); // Delegate 100% to one provider await network.delegateToFtso(wallet, providerAddress, Amount.percentages(100)); // Split 50/50 between two providers await network.delegateToFtso( wallet, provider1Address, Amount.percentages(50), provider2Address, Amount.percentages(50), ); // Remove all delegations await network.undelegateFromFtso(wallet); ``` :::info[Basis points] Delegations are expressed in basis points (1 % = 100 bp). `Amount.percentages()` handles conversion of percent to basis points. ::: ## Stake on P-Chain Staking involves moving funds to the P-Chain, delegating them, and later transferring them back. The SDK automates these steps. 1. **Transfer from C to P-Chain:** This operation performs a C-Chain export followed by a P-Chain import automatically. ```javascript // Transfer 100 FLR from C-Chain to P-Chain await network.transferToP(wallet, Amount.nats(100)); ``` 2. **Delegate on P-Chain:** Once funds are on the P-Chain, you can delegate them. ```javascript const amountToDelegate = Amount.nats(50); // 50 FLR const nodeId = "NodeID-P73B..."; // The ID of the validator, see https://flare-systems-explorer.flare.network/validators const startTime = Math.floor(Date.now() / 1000); // Current unix timestamp const endTime = startTime + 14 * 24 * 60 * 60; // e.g. end in 14 days await network.delegateOnP( wallet, amountToDelegate, nodeId, startTime, endTime, // optional ); ``` 3. **Transfer from P to C-Chain:** After your delegation period ends, move your funds back to the C-Chain. ```javascript // Transfer all available funds from P-Chain back to C-Chain await network.transferToC(wallet); ``` ## Vote on governance proposals Governance proposals allow community members to vote on protocol decisions using their voting power (based on WFLR balance and staking). ```javascript // Get active proposals const proposals = await network.getFoundationProposalIds(); // Fetch details const info = await network.getFoundationProposalInfo(proposalId); ``` ### Proposal states | State | Code | Description | | --------- | ---- | ------------------------ | | PENDING | 0 | Voting not started | | ACTIVE | 1 | Voting in progress | | DEFEATED | 2 | Vote failed | | SUCCEEDED | 3 | Vote succeeded | | QUEUED | 4 | Queued for execution | | EXPIRED | 5 | Expired before execution | | EXECUTED | 6 | Executed | | CANCELED | 7 | Canceled | ### Cast a vote A proposal can be voted on only when its `state` is ACTIVE. ```javascript // Check if a voter has already voted: await network.hasCastVoteForFoundationProposal(cAddress, proposalId); // Indicate direction of vote const support = FoundationProposalSupport.FOR; // or FoundationProposalSupport.AGAINST // Cast vote await network.castVoteForFoundationProposal(wallet, proposalId, support); ``` ### Delegate votes ```javascript // Get current vote power of a voter: // Includes both the voter’s own vote power and any delegated to them. const votePower = await network.getCurrentGovernanceVotePower(cAddress); // Get current delegate: let delegate = await network.getCurrentGovernanceVoteDelegate(cAddress); // If `delegate == 0x0` - No delegate assigned // If `delegate != 0x0` - Voter's own vote power is zero (all delegated) // Delegate all vote power to another address: await network.delegateGovernanceVotePower(wallet, delegateAddress); // Remove delegation: await network.undelegateGovernanceVotePower(wallet); ``` :::info[Historical voting data] You can query a voter's vote power or delegate as applied to a specific proposal: ```javascript const votePower = await network.getVotePowerForFoundationProposal( cAddress, proposalId, ); const delegate = await network.getVoteDelegateForFoundationProposal( cAddress, proposalId, ); ``` Queries **may fail** for older proposals, since historical governance vote data is periodically removed from C-Chain storage. ::: ## Interact with C-Chain contracts The SDK provides helpers for interacting with any smart contract on the C-Chain. ```javascript // Call a read-only contract method const result = await network.invokeContractCallOnC(contractAddress, abi, "methodName", [param1, param2]); // Execute a transaction method that changes state // The `value` parameter is for payable methods await network.invokeContractMethodOnC(wallet, contractAddress, abi, "methodName", value, [param1, param2]); // You can also use contract names for official Flare contracts const contractNames = await network.getFlareContracts(); // See available names await network.invokeContractMethodOnC(wallet, "FtsoRewardManager", abi, "claimReward", ...); ``` :::info The ABI must match the contract interface. For official Flare contracts, i.e. contracts defined in the [`FlareContractRegistry`](/network/solidity-reference/IFlareContractRegistry), names are predefined and easier to use. ::: ## Next steps - Check the [Advanced Features](/network/flare-tx-sdk/advanced-usage) for transaction callbacks and custom wallets. - Read the SDK source code on [GitHub](https://github.com/flare-foundation/flare-tx-sdk). :::tip[Need help or found a bug?] Open an issue on the [flare-foundation/flare-tx-sdk](https://github.com/flare-foundation/flare-tx-sdk) GitHub repository. ::: --- ## Advanced Usage ## Transaction lifecycle tracking Every transaction goes through a series of stages. The SDK allows you to register callbacks at each stage to inspect or intercept the process. This is useful for logging, UI updates, or offline signing workflows. ```plaintext Unsigned ↓ sign Signed (before submission) ↓ submit Submitted (pending confirmation) ↓ confirm Confirmed ``` ### Example ```javascript // 1. Before Signature network.setBeforeTxSignatureCallback(async (data) => { console.log("Transaction prepared for signing:", data.unsignedTxHex); console.log("Transaction type:", data.txType); // Return false to cancel return true; }); // 2. Before Submission network.setBeforeTxSubmissionCallback(async (data) => { console.log("About to submit signed tx:", data.signedTxHex); console.log("Transaction ID:", data.txId); return true; // false = cancel }); // 3. After Submission network.setAfterTxSubmissionCallback(async (data) => { console.log(`Tx ${data.txId} submitted, awaiting confirmation...`); return true; }); // 4. After Confirmation network.setAfterTxConfirmationCallback(async (data) => { console.log(`Tx ${data.txId} confirmed with status: ${data.txStatus}`); }); ``` ## Custom wallets If you're integrating a bespoke signer (custody service, backend signer, or another SDK), implement the [`Wallet`](https://github.com/flare-foundation/flare-tx-sdk/blob/HEAD/src/wallet/index.ts) interface. Choose the C-Chain-only interface or dual-chain (C + P) depending on your needs. :::tip[Security] If implementing your own wallet, ensure private keys are never exposed; use secure signers where possible. ::: ### Required Methods :::info C-Chain support is sufficient for most applications. See the full [Wallet](https://github.com/flare-foundation/flare-tx-sdk/blob/main/src/wallet/wallet.ts) interface. ::: | Method | C-Chain Only | C + P Chain | | -------------------------------------------------------- | ------------ | ----------- | | `getCAddress(): Promise` | ✅ | ✅ | | `signCTransaction(tx: string): Promise` | ✅ | ✅ | | `signAndSubmitCTransaction(tx: string): Promise` | ✅ | ✅ | | `signDigest(digest: string): Promise` | ✅ | ✅ | | `getPublicKey(): Promise` | - | ✅ | | `signPTransaction(tx: string): Promise` | - | ✅ | | `signEthMessage(message: string): Promise` | - | ✅ | ### Minimal ethers.js wrapper example This example (using ethers.js v6) shows the shape - adapt to your transport and security model. This assumes your signer supports `signTransaction`, `sendTransaction`, and `getAddress`. ```typescript // ^ type path: see src/wallet/index.ts in the repo Signer, Transaction, getBytes, type TransactionRequest, type Provider, } from "ethers"; export class EthersWallet implements SdkWallet { constructor(private readonly signer: Signer) {} async getCAddress(): Promise { return await this.signer.getAddress(); } async signCTransaction(unsignedTxHex: string): Promise { // Parse the (unsigned) serialized tx into a Transaction object const tx = Transaction.from(unsignedTxHex); if (tx.signature) { throw new Error( "Expected unsigned transaction, but a signature was present.", ); } const request: TransactionRequest = { to: tx.to ?? undefined, data: tx.data ?? undefined, value: tx.value ?? undefined, gasLimit: tx.gasLimit ?? undefined, maxFeePerGas: tx.maxFeePerGas ?? undefined, maxPriorityFeePerGas: tx.maxPriorityFeePerGas ?? undefined, nonce: tx.nonce, chainId: tx.chainId, type: tx.type, }; return await this.signer.signTransaction(request); } async signAndSubmitCTransaction(unsignedTxHex: string): Promise { const rawSigned = await this.signCTransaction(unsignedTxHex); const provider: Provider | null = this.signer.provider; if (!provider) throw new Error("Signer has no provider for submission."); const sent = await provider.sendTransaction(rawSigned); await sent.wait(); // or omit if you want fire-and-forget return sent.hash; } async signDigest(digestHex: string): Promise { // This signs the BYTES with the EIP-191 prefix. return await this.signer.signMessage(getBytes(digestHex)); } async signEthMessage(message: string): Promise { return await this.signer.signMessage(message); } } ``` :::tip[Need help or found a bug?] Open an issue on the [flare-foundation/flare-tx-sdk](https://github.com/flare-foundation/flare-tx-sdk) GitHub repository. ::: --- ## Weights and Signing ## Data Providers Data providers are offchain participants in Flare's protocols, responsible for threshold-weighted voting across all sub-protocols. The system anticipates **100 data providers**, selected in a decentralized manner among validators with the highest **vote power**. Importantly, vote power is community-drive, derived from two key sources: - **P-chain Stake**: `FLR` staked either directly by validators or delegated to them by other network participants. - **WNat Delegations**: Wrapped `FLR` (`WFLR`) delegated by community members via the WNat smart contract. This design ensures that governance over Flare's protocols is not dictated by a centralized entity, but instead emerges organically from the collective decisions of token holders and validators. Through delegation and staking, the community directly shapes the behavior and evolution of the network. Each data provider is identified by an **identity address** (managed securely via cold wallets), used for conducting admin operations (e.g., setting fees, signing addresses, delegation addresses). Data providers can also set a **signing address** (hot wallet) for protocol participation, and an optional prioritized submission address for onchain communication. ## Voting Within the core Flare protocols, time is divided in units, as follows: - **Voting Epoch**: The shortest time unit, lasting **90 seconds**. - **Reward Epoch**: Comprises **3360 voting epochs** (approx. 3.5 days). A new voting epoch (or reward epoch, respectively) starts immediately after the previous one concludes. The voting frequency of each sub protocol is usually once per voting epoch, while some system protocols have voting frequency per reward epoch. For specific sub-protocols, a **voting round** represents a full voting sequence that starts in a particular voting epoch and typically extends to the next one. As a result, voting rounds are identified by the ID of the voting epoch in which they started. ### Voting results and finalization Each sub-protocol aims to reach consensus on a **Merkle root** for every voting round: - Data providers independently compute candidate Merkle roots and submit signed roots onchain. - A root is confirmed if it surpasses a **50%+ voting weight threshold**, as defined below. - Finalization occurs when signatures exceeding the threshold are submitted to the [`Relay`](/network/fsp/solidity-reference/IRelay) contract, which verifies and stores confirmed Merkle roots. These are accessible for proof verification by other smart contracts. ### Using confirmed voting results To verify data onchain: 1. Smart contracts receive: - Protocol data (structured as a Solidity struct). - Voting round ID. - Merkle proof. 2. The smart contract encodes the data and applies the Merkle proof to match the confirmed root from the [`Relay`](/network/fsp/solidity-reference/IRelay) contract. 3. If the roots match, the data is verified. ## Signing Policies and Weight Systems Data provider eligibility and weights are determined for each **reward epoch**. The [**Signing Policy Definition Protocol**](/network/fsp/system-protocols) sets the eligible data providers, their weights, and the threshold required for confirmation before each reward epoch begins. Within the FSP and core Flare protocols, there are multiple types of weights, defined as follows: - **P-chain Stake ($W_P$)**: The amount of long-term staked `FLR` tokens on the P-chain to a particular data provider. This includes both self-bond as well as delegated stake on all validator nodes assigned to that data provider. This weight is only available on the P-chain. - **Mirrored Stake ($W_M$)**: Mirroring is the process of recording P-chain state to smart contracts on the C-chain. As such, the mirrored stake is roughly similar to the P-chain stake, though it can for short periods (the mirroring duration) differ from the actual stake on the P-chain. - **WNat Delegations ($W_{WFLR}$)**: The vote power on the WNat contract, based on both delegated and owned `WFLR` (or `WSGB` for the Songbird Canary-Network, respectively). - **Capped Delegation Weight ($W'_{WFLR}$)**: The delegation weight, $W_{WFLR}$, limited to 2.5% of total `WFLR` weight. - **Registration Weight ($W_{reg}$)**: The weight used when data providers are registered before each reward epoch, being defined as: $$ W_{reg} = \left(W'_{WFLR} + W_M \right)^{0.75}~. $$ - **Signing Weight ($W_{S}$)**: Also known as normalized weight, this is a normalized registration weight such that the sum of all signing weights gets packed into 2-bytes. - **Total Voting Weight**: Calculated by summing up the signing weights of all eligible data providers. Weight systems serve a variety of purposes in Flare's management and enshrined protocols, allowing community members to play an important role in Flare's core protocols. The most widely used weight system throughout the main protocols is the **signing weight**, which is part of the **signing policy**. The relevant weights and their uses are listed below. | **Protocol** | **Component** | **Weight System** | | -------------- | ----------------------------------------------------------------------------------------------- | ----------------- | | **FSP** | • Updating Signing Policy• Validator Uptime• Reward Signing | $W_S$ | | **FTSOv2** | • Scaling: Weighted Median Calculation• Scaling: Accuracy Rewards | $W'_{WFLR}$ | | | • Scaling: Signing and Finalization (through FSP)• Block-latency: Sortition and Rewarding | $W_S$ | | **FDC** | • Bit Voting• Signing and Finalization (through FSP)• Rewarding | $W_S$ | | **Staking** | • Staking Rewards | $W_P$ | | **Governance** | • Voting | $W_{WFLR} + W_P$ | ### Signing Policy thresholds A **signing policy** includes: - `rewardEpochId`: Reward epoch ID. - `startVotingRoundId`: Indicates the start of a reward epoch. - `voters`: Canonical list of data provider addresses. - `weights`: Compressed, normalized weights (2-byte values). - `threshold`: Usually set to **50%+** of total weight. - `seed`: Secure random seed. The signing policy enables weighted voting, with acceptance **thresholds** set to: - Regular threshold: **50%+** - In case of delays: **60%+** ### Signature verification Signature verification is done through standard ECDSA signatures. The [`Relay`](/network/fsp/solidity-reference/IRelay) contract receives a signing policy in calldata and prior to finalization it verifies whether the signing policy is supported on the contract. --- ## Rewarding Flare's sub-protocols utilize a uniform signing weight for decision-making; however, reward distribution is based on the delegators' participation weights. Each sub-protocol calculates rewards for various participation weight types, allowing delegators to claim rewards according to their specific contribution shares. All rewards are managed through the [`RewardManager`](/network/fsp/solidity-reference/IRewardManager) contract and are claimable after the reward epoch concludes. Sub-protocols implement their own contracts to gather rewarding inputs, which are passed to the Reward Manager for distribution. Funding is provided by sub-protocol-specific contracts, which may also handle inflation-based rewards through automated offers. ## Minimal Conditions The [FIP.10](https://proposals.flare.network/FIP/FIP_10.html) and [SIP.04](https://proposals.flare.network/SIP/SIP_4.html) governance proposals introduced an incentive structure for participating in all protocols. The proposals implemented a set of minimum participation requirements from the data providers in a reward epoch for all Flare protocols. These proposals ensure that participation in Flare's **enshrined protocols** is not merely symbolic, but actively upheld by the network's validators through meaningful engagement. Failure to meet **all** these requirements can ultimately lead to loss of rewards across all protocols. ### Passes The incentive structure introduces the concept of **passes**. Each provider has a number of passes, and can gain or lose passes depending on whether they meet the minimum participation requirements for each protocol. - Newly registered data providers start with zero passes. - When a data provider meets the minimum requirements in a reward epoch, they gain a pass. Providers can hold a maximum of 3 passes. - When a data provider fails to meet the minimum requirements in a reward epoch, they lose a pass. Providers can lose one pass per protocol per reward epoch. - If a provider would lose a pass but doesn't have any, they lose all rewards for the epoch. That is, a provider with 1 pass who fails to meet 1 minimal requirement will have 0 passes but will still achieve rewards, and a provider with 0 passes who fails to achieve a minimal requirement loses all rewards. ### Minimum participation requirements Each Flare protocol implements the minimum participation requirements, defined across each reward epoch independently. FTSO and Staking requirements were introduced in [FIP.10](https://proposals.flare.network/FIP/FIP_10.html), while FDC requirements were defined in [FIP.12](https://proposals.flare.network/FIP/FIP_12.html). For completeness, these are also listed below: - **Staking**: Providers must meet 80% total uptime in the reward epoch with at least 1M FLR in active self-bond. However, in order to earn passes, the provider must have at least 3M FLR in active self-bond and 15M in active stake. Providers with 80% total uptime and at least 1M FLR in active self-bond but not meeting both the 3M FLR active self-bond and 15M active stake requirements neither earn nor lose passes, and still receive eligible rewards. - **FTSO anchor feeds**: Providers must submit a value estimate that lies within a 0.5% band around the consensus median value in 80% of voting rounds within a reward epoch. - **FTSO block-latency feeds**: Providers must submit at least 80% of their expected number of updates within a reward epoch, unless they have very low weight, defined as < 0.2% of the total active weight. - **FDC**: Data providers must successfully participate in 60% of all voting rounds within a reward epoch. That is, they must submit the correct Merkle root for that voting epoch. ## Reward Claim structure All rewards are distributed through **reward claims**, which are data records indicating the allocation of rewards. Each reward claim consists of the following fields: - `rewardEpochId`: Identifier for the reward epoch. - `beneficiary`: The address of the reward beneficiary or node ID (20-bytes). - `amount`: Reward amount in `FLR`. - `claimType`: The category of the reward claim, which can be one of the following: - `direct`: Rewards directly attributed to the beneficiary (address). These claims are used for undistributed rewards, fund providers, burn claims, or specific rewarding approaches within sub-protocols. - `fee`: Similar to `direct` claims, these are also rewards directly attributed to the beneficiary address, which, in this case, can only be an eligible data provider for the given reward epoch. The fees cover `WFLR` delegation fees and node staking fees. - `wflr`: Rewards that should be distributed to community delegators according to their share of `WFLR` (i.e., their contribution to the $W_{WFLR}$ weight). The beneficiary is an eligible data provider for the given reward epoch. - `mirror`: Rewards that should be distributed to stake delegators according to their contribution to the stake-mirrored $W_{M}$ weight. The beneficiary is an eligible data provider for the given reward epoch. Weight-based claims (`wflr` and `mirror`) represent total rewards for data providers, to be distributed according to the delegators' participation in the data providers' weight. We refer to the [Weights and Signing](/network/fsp/weights-and-signing) page for more details on how these weights are calculated. ### Calculation process :::tip[Calculating or verifying rewards data] All rewards scripts are publicly available, and data providers are encouraged to calculate verify the rewards data for themselves. The data for the rewards is published on [flare-foundation/fsp-rewards](https://github.com/flare-foundation/fsp-rewards) and the instructions for how to calculate them are [here](https://github.com/flare-foundation/FTSO-Scaling/blob/main/scripts/rewards/README.md#public-reward-data). ::: Each sub-protocol is responsible for calculating its **partial reward claims** for each reward epoch by: 1. **Data Input**: Determining the relevant data sources (indexers). 2. **Data Retrieval**: Querying data from indexers. 3. **Reward Calculation**: Implementing reward algorithms to produce partial reward claims. Partial reward claims may have a negative amount if penalization is applied. This allows misbehavior in one sub-protocol to reduce rewards earned in others, enforcing accountability across the entire system. The [Reward Voting Protocol](/network/fsp/system-protocols) aggregates these partial claims into a single claim per beneficiary and type. The final claims are structured into a Merkle tree, and the Merkle root is confirmed through a voting process. ### Claiming process Once the Merkle root is confirmed for a reward epoch, rewards can be claimed via the [`RewardManager`](/network/fsp/solidity-reference/IRewardManager) contract: - For `direct` or `fee` claims, rewards are transferred directly to the beneficiary once the submitted proof is verified. - For **weight-based claims**, the process involves two steps: 1. **Initialization**: Submission of a reward claim with a Merkle proof to initialize contract variables. 2. **Delegator Claims**: Delegators can then claim their share without needing additional proofs. Once initialized, delegators can claim rewards through the [`RewardManager`](/network/fsp/solidity-reference/IRewardManager) contract. For a given reward epoch, the reward allocated to delegator $x$ associated to a specific data provider (beneficiary address) is calculated as: $$ \text{reward} = \frac{w(T, x)}{U_{T}^{weight}}\times U_{T}^{amount}~, $$ where: {/* prettier-ignore */} - $ w(T, x) $ is the weight delegated by delegator $x$ to the beneficiary address for weight type $T$ (i.e. either delegated stake or delegated `WFLR`) during the specified reward epoch. {/* prettier-ignore */} - $ U_{T}^{weight} $ is the total yet unclaimed delegation weight associated with the beneficiary address and weight type $T$, for that epoch. {/* prettier-ignore */} - $ U_{T}^{amount} $ is the total yet unclaimed reward amount for the same beneficiary and epoch. Once claimed, both the unclaimed amount and weight are reduced accordingly, and the payout is transferred directly to delegator $x$. ## Incentivizing fast signing and finalization Sub-protocols are designed to encourage fast signature deposition and finalization: 1. **Signing Deposition Rewards**: - Data providers submitting signatures within a grace period (10s) from the signature start time will automatically receive rewards. - Signatures submitted until the block of finalization are also rewarded. - If finalization happens after a **hard limit**, the end of the voting round during which the signing round started, then only those signatures submitted before the hard limit are considered for rewards, and only if they exceed a 30% weight threshold. 2. **Finalization Rewards**: - Finalization of a Merkle root can be carried out only once, by the first entity to collect sufficient signing weight of signatures and send them to the voting contract. - During a 20s grace period, a set of selected data providers gets rewarded either for successful finalization or an attempt of it. (i.e. If the call got reverted due to data being already finalized.) - If finalization is not carried out within the grace period, any other data provider is allowed to finalize, and the first entity to do so receives the full finalization reward. - Just like with signature deposition, finalization rewards are only available within a **hard cutoff**: finalization must occur before the end of the voting epoch following the one in which the signature round started. The diagram below depicts how the signing and finalization grace periods and hard limits are applied for vote epoch $k$. ### Selection of Data Providers for finalization During the grace period for finalization, selected data providers are rewarded based on a pseudo-random selection algorithm: - A pseudo-random number is generated by hashing the protocol id and the voting round number. - Data providers are selected with probability proportional to their signing weight in the active policy, using the previously generated pseudo-random number. - Data providers are drawn sequentially until the total selected weight reaches a minimum threshold (set to 5% of total signing weight). - The selected providers are rewarded proportionally to their individual weights relative to the total selected weight. ### Reward distribution Each sub-protocol allocates rewards for **signing deposition** and **finalization**. The reward amount attributed to a data provider is split into: - **Fee Claims**: Deducted as delegation fees (WNat delegations). - **Weight Participation Claims**: Remaining rewards are distributed among delegators and stakers based on their weights. --- ## System Protocols The **Flare Systems Protocol** encompasses the following core system protocols: 1. **Signing Policy Definition Protocol** 2. **Validator Uptime Voting Protocol** 3. **Reward Voting Protocol** 4. **Random Number Generation Protocol** (dependent on [FTSOv2 Scaling](/ftso/scaling/overview)) Protocols with voting frequencies tied to reward epochs are implemented directly on the [`FlareSystemsManager`](/network/fsp/solidity-reference/IFlareSystemsManager) smart contract, while others operate as sub-protocols. Each sub-protocol has a unique protocol ID, except for the system protocols mentioned above. ## Signing Policy Definition Protocol The Signing Policy Definition Protocol is a system protocol which ensures that data providers and their weights for the next reward epoch are locked and signed by a threshold weight of current data providers. The protocol works in phases, as follows: - **Random Number Acquisition**: Around 2 hours before the end of the current reward epoch, a random number is fetched through the [FTSOv2 Scaling](/ftso/scaling/overview) protocol. - **Vote Power Block Selection**: Once a random number is obtained a block from the current reward epoch is selected at random. The vote power registered in this selected block will be used for the new reward epoch. - **Voter Registration**: Once a random number is obtained, registration for the new reward epoch can begin. This lasts 30 minutes, allowing self-registration based on the selected block's vote power. - **Signing Policy Snapshot**: When the voter registration phase ends, a snapshot of the data providers' addresses and weights is taken. - **Signing Policy Sign Phase**: After the voter registration phase, the signing phase begins. In this phase the data providers registered for the ongoing epoch submit signatures for updating the signing policy. This phase ends when the threshold of signatures is reached. Delays beyond 20 minutes or 600 blocks will incur penalties. While there are no direct rewards, delays in finalization cause a global lock of reward claiming and additional penalization when claiming is finally allowed. More specifically, each signer gets punished with burn of their fee claims, scaled quadratically with the length of the delay. ## Validator Uptime Voting Protocol Following each reward epoch, data providers submit signed lists of validator NodeIDs that, in their view, achieved sufficient uptime (≥80%) during the preceding epoch. The voting process concludes upon reaching a defined threshold weight. Since the up-time data is needed for calculation of the rewards, the lack of a successful finalization blocks the execution of the reward voting protocol. ## Reward Voting Protocol After each reward epoch, and once uptime voting is complete, data providers calculate cumulative reward claims for all sub protocols. The calculation of rewards takes into account all inputs and onchain records of participation of data providers in a specific sub protocol. Reward claims across all sub-protocols are aggregated into a Merkle tree. Voting takes place for the corresponding Merkle root, and, once confirmed, the `RewardManager` contract can verify individual claims and execute reward payouts. We refer to [Rewarding](/network/fsp/rewarding) for more details on how rewards are calculated and claimed. ## Random Number Generation Protocol Random numbers are generated as a byproduct of the [FTSOv2 Scaling](/ftso/scaling/overview) protocol, which employs a Commit & Reveal scheme. In this scheme, each commitment includes not only feed values but also a locally generated random number. This random number serves to blind the user's commit hash, protecting it against search-based attacks. Once the reveal phase begins, the random numbers from valid reveals are aggregated into a single random value for the current voting epoch. This aggregated random number is included in the Merkle root for the epoch, confirmed via the [`Relay`](/network/fsp/solidity-reference/IRelay) contract. --- ## Offchain Services The **Flare Systems Protocol** utilizes a set of offchain services encapsulated within the **Flare Systems Client**. These services interact with blockchain smart contracts to support various protocols. Key components include: 1. **Protocol Manager Service**: Handles periodic transactions for each voting round. The content of these transactions depends on the participation across the sub-protocols. 2. **Reward Aggregator Service**: Submits the Merkle root of combined reward claims once per reward epoch. 3. **Signing Policy Voter Service**: Signs new signing policies after they are defined, once per reward epoch. 4. **Voter Registration Service**: Executes data provider registration on the [`VoterRegistry`](/network/fsp/solidity-reference/IVoterRegistry) contract. 5. **Finalizer Service**: Submits finalization transactions when a data provider is eligible to finalize a specific sub-protocol. 6. **Scheduler**: Coordinates transaction scheduling across all other services. 7. **Uptime Voting Client**: Submits validator uptime votes once per reward epoch. Each data provider runs an independent instance of the [Flare Systems Client](https://github.com/flare-foundation/flare-system-client), which manages private keys and transaction submissions, enabling participation across multiple sub-protocols. ## Protocol Manager Service The **Protocol Manager Service** sends the following transactions within each voting epoch: - `submit1`, `submit2`, `submit3`: Data submission at fix scheduled times during the voting epoch. - `submitSignatures`: Submits signatures only once all required data is collected. **Data Flow**: 1. The service queries protocol specific data sources via API routes to fetch data. 2. The collected data is processed, encoded into byte sequences, and sent in transaction calldata as: ``` tx_data = function_selector + concatenated_data ``` where each payload includes: - `protocolId`: the protocol id. - `votingRoundId`: the id of the voting round. - `size`: the number of bytes in the payload. - `payload`: protocol specific data encoded protocol data encoded into bytes. **API Endpoints and Response Format**: - `GET /submit1/:votingRoundId/:submitAddress` - `GET /submit2/:votingRoundId/:submitAddress` - `GET /submitSignatures/:votingRoundId/:submitSignaturesAddress` - `GET /submit3/:votingRoundId/:submitAddress` - Response Format: ```json { "status": "OK", "data": "0x1234...", "additionalData": "0x5678..." } ``` The services are data provider-agnostic, requiring only `votingRoundId` and `submitAddress` as inputs. ## Reward Aggregator Service The **Reward Aggregator Service** calculates and submits the Merkle root of reward claims at the end of each reward epoch: - Fetches reward data from protocol reward calculators using C-chain and P-chain indexers. - Submits the final Merkle root via `signRewards`. **API Endpoints and Response Format**: - `GET /rewards/:rewardEpochId` - Response Format: ```json { "status": "OK", "data": "0xabc123..." } ``` ## Signing Policy Voter Service The **Signing Policy Voter Service** monitors the `SigningPolicyInitialized` event on the [`Relay`](/network/fsp/solidity-reference/IRelay) contract. This event occurs when a new signing policy is defined. The service has the following roles: - Detect new signing policy creation event. - Signs the new policy using the `signNewSigningPolicy` function. - Tracks `SigningPolicySigned` events on the [`FlareSystemsManager`](/network/fsp/solidity-reference/IFlareSystemsManager) contract in order to determine if further signatures are needed, and to confirm their vote. ## Voter Registration Service The **Voter Registration Service** listens to events on the [`FlareSystemsManager`](/network/fsp/solidity-reference/IFlareSystemsManager) contract in order to register a data provider for the next epoch. The service works as follows: - Listens for `VotePowerBlockSelected` events on the [`FlareSystemsManager`](/network/fsp/solidity-reference/IFlareSystemsManager) contract. - Registers the data provider via the `registerVoter` method on the [`VoterRegistry`](/network/fsp/solidity-reference/IVoterRegistry) contract before the `SigningPolicyInitialized` event signals the end of the registration period. ## Finalizer Service The **Finalizer Service** monitors `submitSignatures` function calls on indexer and carries out finalization for every voting round. The service works as follows: - Collect signatures from various `submitSignatures` transactions. - Once a sufficient weight of signatures is gathered for a specific voting round, the service is ready to submit finalization data to the [`Relay`](/network/fsp/solidity-reference/IRelay) contract. - The finalizer service then decides whether to send the finalization data, by checking the data provider's eligibility for rewards. Namely, during the **grace period**, submissions from randomly selected data providers are reward eligible, as discussed in more detail in [Rewarding](/network/fsp/rewarding). - If eligible, then the service submit the data to the [`Relay`](/network/fsp/solidity-reference/IRelay) contract via the `relay` function. Importantly, finalizations sent within the grace period must be sent through the signing address (i.e. the address registered on the signing policy). ## Data submissions ### Data encoding and payloads Data for `submitSignatures` is structured as follows: - `type` (1 byte): Message type (0 or 1). - `message` (38 bytes): - `protocolId` (1 byte) - `votingRoundId` (4 bytes) - `randomQualityScore` (1 byte) - `merkleRoot` (32 bytes) - `signature` (65 bytes): ECDSA signature components of the message (`v`, `r`, `s`). - `unsignedMessage` (optional): Additional data (e.g., revealed random number). ### Data availability and Merkle trees Each sub-protocol assembles a certain Merkle trees using offchain data: - The data used for the Merkle tree is obtained via the `GET /data/:votingRoundID` endpoint. The response format should be of the form: ```json { "status": "OK", "data": [{"abiName": "StructName", "data": {...}}] } ``` - ABI definitions are accessible via `GET /data-abis`, with an expected response format: ```json { "status": "OK", "data": JSONAbiDefinition[] } ``` ### Storage and calculation model The data used in calculation for Merkle trees is all timestamped and includes the following types: - Events emitted by smart contracts. - Calldata from specific contract calls. - Immutable contract values, indexed by time. The **Flare blockchain indexer** enables querying by time intervals and event types. Data providers use the indexer to fetch data and perform calculations, which are then encoded into Merkle roots. The benefits of the proposed storage and calculation model include: - Support for complex calculations beyond Solidity's capabilities. - Reduced storage costs by storing only Merkle roots onchain. ### Result availability and APIs Data providers assemble Merkle roots for voting and can provide services to access confirmed data. This data can be exposed via APIs, allowing users to obtain calculation results with full Merkle proofs for onchain verification. ### Transaction prioritization The `Submission` smart contract prioritizes key transactions (`submit1`, `submit2`, `signatureDeposit`) to subsidize gas costs. Multiple sign transactions are allowed, but only one subsidized submission is permitted per voting round. --- ## Protocol Parameters Each FSP sub-protocol (e.g. FTSOv2, FDC) has several associated parameters, which are detailed here, alongside the contract method (if applicable) to query them onchain. The values provided here correspond to Flare Mainnet. ## FTSOv2 ### Block-Latency Feeds (Fast Updates) | Metric | Value | Associated functions | | ------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- | | **Mean updates per block** | 1.5 | [`FastUpdatesIncentiveManager.getExpectedSampleSize`](/ftso/solidity-reference/IFastUpdateIncentiveManager#getexpectedsamplesize) | | **Precision (step size)** | ≈0.0122% | [`FastUpdatesIncentiveManager.getPrecision`](/ftso/solidity-reference/IFastUpdateIncentiveManager#getprecision) | | **Range (precision \* mean updates)** | 0.0183% | [`FastUpdatesIncentiveManager.getRange`](/ftso/solidity-reference/IFastUpdateIncentiveManager#getrange) | | **Max range** | 0.091% (5x) | [`FastUpdatesIncentiveManager.rangeIncreaseLimit`](/ftso/solidity-reference/IFastUpdateIncentiveManager#rangeincreaselimit) | | **Incentive duration** | 17 blocks | governance-configured (see [`IFastUpdateIncentiveManager`](/ftso/solidity-reference/IFastUpdateIncentiveManager)) | | **Incentive fee (2x)** | ≈36.6 FLR | - | ### Anchor Feeds (Scaling) | Metric | Value | Associated functions | | ------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | **Update duration** | 90s | - | | **FTSO protocol ID** | 100 | [`FtsoFeedPublisher.ftsoProtocolId`](/ftso/scaling/solidity-reference/IFtsoFeedPublisher#ftsoprotocolid) | | **Feeds history size** | 1000 | [`FtsoFeedPublisher.feedsHistorySize`](/ftso/scaling/solidity-reference/IFtsoFeedPublisher#feedshistorysize) | | **Minimal reward offer** | 100000 FLR | [`FtsoRewardOffersManager.minimalRewardsOfferValueWei`](/ftso/scaling/solidity-reference/IFtsoRewardOffersManager#minimalrewardsoffervaluewei) | --- ## FSP Reference ## Deployed Contracts export const contracts = [ "EntityManager", "Submission", "FlareSystemsManager", "Relay", "RewardManager", "VoterRegistry", "FlareSystemsCalculator", "WNatDelegationFee", "ValidatorRewardManager", ]; ## Interfaces --- ## Claiming rewards This guide shows how to claim Flare rewards directly via smart contracts, using the published per-epoch Merkle distributions. You will: - read the claimable reward epoch range onchain, - fetch the distribution tuples JSON offchain, - extract your Merkle proof + claim tuple, - submit `RewardManager.claim(...)`, - claim staking rewards `ValidatorRewardManager.claim(...)`. :::warning[Reward claim period] Delegation rewards expire after 25 reward epochs, make sure to claim before then. Staking rewards do not expire. Learn more about how [signing](/network/fsp/weights-and-signing) and [rewards](/network/fsp/rewarding) work in the FSP. ::: ## Prerequisites 1. **Wallet + gas:** A wallet that can sign transactions on the target network, funded with enough native token to pay gas. 2. **RPC access:** An RPC endpoint for the chosen network (you can use any RPC listed on the [Network](/network/overview) page). 3. **Contracts:** Addresses can be found in the [Flare Contract Registry](/network/guides/flare-contracts-registry): - [`FlareSystemsManager`](/network/fsp/solidity-reference/IFlareSystemsManager) - [`RewardManager`](/network/fsp/solidity-reference/IRewardManager) - [`ClaimSetupManager`](/network/solidity-reference/IClaimSetupManager) - [`ValidatorRewardManager`](/network/fsp/solidity-reference) 4. **Recipient address:** The address that will receive the rewards (`recipient`). 5. **Reward distribution data access:** You must be able to fetch per-epoch `reward-distribution-data-tuples.json` from the official distribution locations on GitHub or GitLab. 6. **Wrap option:** `wrapRewards` is `true` unless explicitly set to `false`, i.e. on Flare Mainnet you will receive `WFLR`. 7. **Beneficiary address (depends on claim type):** | Claim type | `claimType` | `beneficiary` must be | Account that must authorize executors | | ---------- | ----------: | ---------------------- | ------------------------------------- | | `DIRECT` | `0` | signing policy address | signing policy address | | `FEE` | `1` | identity address | identity address | ### Recommended: Configure a claim executor and claim recipient For improved security, set up a **claim executor** (an account authorized to claim on your behalf) and an **allowed claim recipient** (an address permitted to receive rewards). 1. Create and fund a new account with sufficient native tokens 2. Authorize this account as a claim executor, which can be done using the [`setClaimExecutors`](/network/solidity-reference/IClaimSetupManager#setclaimexecutors) method. - For **DIRECT (0)** claims: Use your signing policy account to authorize the executor. - For **FEE (1)** claims: Use your identity account to authorize the executor. 3. Setup a **Claim Recipient** account, this can be done through the [`setAllowedClaimRecipient`](/network/solidity-reference/IClaimSetupManager#setallowedclaimrecipients) method. ```ts const claimSetupManager = new Contract( process.env.CLAIM_SETUP_MANAGER_ADDRESS!, // from contract registry CLAIM_SETUP_MANAGER_ABI, beneficiarySigner, // IMPORTANT: signer must be the signing policy address (`DIRECT`) or identity address (`FEE`) ); // 1) Authorize the executor that will submit RewardManager.claim(...) await (await claimSetupManager.setClaimExecutors([executorAddress])).wait(); // 2) Allowlist a recipient address that is permitted to receive rewards await ( await claimSetupManager.setAllowedClaimRecipients([recipientAddress]) ).wait(); ``` ## Step-by-step ### 1. Connect to the contracts Instantiate contract clients (ABI + address) for: - [`FlareSystemsManager`](/network/fsp/solidity-reference/IFlareSystemsManager) - [`RewardManager`](/network/fsp/solidity-reference/IRewardManager) Example using [ethers v6](https://docs.ethers.org/v6/): ```ts // 1) Provider + signer const provider = new JsonRpcProvider(process.env.RPC_URL); const signer = new Wallet(process.env.CLAIM_EXECUTOR_PRIVATE_KEY!, provider); // 2) Contract instances (ABI omitted here; use your generated ABI or interface) const flareSystemsManager = new Contract( process.env.FLARE_SYSTEMS_MANAGER_ADDRESS!, FLARE_SYSTEMS_MANAGER_ABI, provider, ); const rewardManager = new Contract( process.env.REWARD_MANAGER_ADDRESS!, REWARD_MANAGER_ABI, provider, ); ``` ### 2. Read the claimable epoch range Call: 1. `startRewardEpochId = RewardManager.getNextClaimableRewardEpochId(beneficiary)` 2. `[_, endRewardEpochId] = RewardManager.getRewardEpochIdsWithClaimableRewards()` If `endRewardEpochId < startRewardEpochId`, there is nothing to claim. Example: ```ts const start = await rewardManager.getNextClaimableRewardEpochId(beneficiary); const [, end] = await rewardManager.getRewardEpochIdsWithClaimableRewards(); if (end < start) { console.log("Nothing claimable for this beneficiary."); } ``` ### 3. Keep only signed epochs For each `epochId` in `[startRewardEpochId … endRewardEpochId]`, call: - `rewardsHash = FlareSystemsManager.rewardsHash(epochId)` Only proceed if `rewardsHash` is **not** `0x000...000` (`bytes32(0)`). Example: ```ts const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; const signedEpochs: bigint[] = []; for (let epochId = start; epochId <= end; epochId++) { const rewardsHash = await flareSystemsManager.rewardsHash(epochId); if (rewardsHash && rewardsHash !== ZERO_BYTES32) signedEpochs.push(epochId); } ``` ### 4. Fetch reward distribution tuples :::tip[Calculating or verifying rewards data] All rewards scripts are publicly available, and you are encouraged to calculate/verify the rewards data yourself. The data for the rewards is published on [flare-foundation/fsp-rewards](https://github.com/flare-foundation/fsp-rewards) and the instructions for how to calculate them are [here](https://github.com/flare-foundation/FTSO-Scaling/blob/main/scripts/rewards/README.md#public-reward-data). ::: For each signed epoch, fetch `reward-distribution-data-tuples.json`. You fetch **one JSON file per epoch**, using the epoch id in the path. If the JSON cannot be fetched or validated, you cannot build a valid Merkle proof for that epoch. Fetch `reward-distribution-data-tuples.json` from [flare-foundation/fsp-rewards](https://github.com/flare-foundation/fsp-rewards) on GitHub. ```bash EPOCH_ID=123 BENEFICIARY="0xYourBeneficiaryAddressHere" CLAIM_TYPE=0 curl -fsSL \ "https://raw.githubusercontent.com/flare-foundation/fsp-rewards/refs/heads/main/flare/${EPOCH_ID}/reward-distribution-data-tuples.json" \ | jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r ' .rewardClaims[] | select(.[1][1] | ascii_downcase == ($b | ascii_downcase)) | select(.[1][3] == $ct) ' ``` Fetch `reward-distribution-data-tuples.json` from the community-maintained mirror [timivesel/ftsov2-testnet-rewards](https://gitlab.com/timivesel/ftsov2-testnet-rewards) on GitLab. (This mirror is not an official Flare Foundation resource; treat the data with appropriate care and verify each tuple against on-chain values before claiming on production systems.) ```bash EPOCH_ID=123 BENEFICIARY="0xYourBeneficiaryAddressHere" CLAIM_TYPE=0 curl -fsSL \ "https://gitlab.com/timivesel/ftsov2-testnet-rewards/-/raw/main/rewards-data/coston2/${EPOCH_ID}/reward-distribution-data-tuples.json" \ | jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r ' .rewardClaims[] | select(.[1][1] | ascii_downcase == ($b | ascii_downcase)) | select(.[1][3] == $ct) ' ``` Fetch `reward-distribution-data-tuples.json` from [flare-foundation/fsp-rewards](https://github.com/flare-foundation/fsp-rewards) on GitHub. ```bash EPOCH_ID=123 BENEFICIARY="0xYourBeneficiaryAddressHere" CLAIM_TYPE=0 curl -fsSL \ "https://raw.githubusercontent.com/flare-foundation/fsp-rewards/refs/heads/main/songbird/${EPOCH_ID}/reward-distribution-data-tuples.json" \ | jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r ' .rewardClaims[] | select(.[1][1] | ascii_downcase == ($b | ascii_downcase)) | select(.[1][3] == $ct) ' ``` Fetch `reward-distribution-data-tuples.json` from the community-maintained mirror [timivesel/ftsov2-testnet-rewards](https://gitlab.com/timivesel/ftsov2-testnet-rewards) on GitLab. (This mirror is not an official Flare Foundation resource; treat the data with appropriate care and verify each tuple against on-chain values before claiming on production systems.) ```bash EPOCH_ID=123 BENEFICIARY="0xYourBeneficiaryAddressHere" CLAIM_TYPE=0 curl -fsSL \ "https://gitlab.com/timivesel/ftsov2-testnet-rewards/-/raw/main/rewards-data/coston/${EPOCH_ID}/reward-distribution-data-tuples.json" \ | jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r ' .rewardClaims[] | select(.[1][1] | ascii_downcase == ($b | ascii_downcase)) | select(.[1][3] == $ct) ' ``` ### 5. Extract your Merkle proof + claim tuple In the fetched JSON, locate an entry in `rewardClaims` where: - `address` matches your `beneficiary` (case-insensitive match offchain, exact value used onchain) - `claimType` equals your target `claimType` (`0` for DIRECT, `1` for FEE) Each matching entry provides: - `merkleProof`: array of hex strings - tuple `[id, address, sum, claimType]` :::info You do **not** submit the JSON onchain. You only extract your `merkleProof` and tuple from `rewardClaims` to build the `claims[]` argument passed to `RewardManager.claim(...)`. ::: Build a [`RewardClaimWithProof`](/network/fsp/solidity-reference/IRewardManager#rewardclaimwithproof) struct from the JSON: ```ts { merkleProof: string[], body: { rewardEpochId: bigint, // BigInt(id) beneficiary: string, // address amount: bigint, // BigInt(sum) claimType: bigint // BigInt(claimType) } } ``` **Important:** `rewardEpochId` is taken from the tuple's `id`. Example: ```ts type ClaimType = 0 | 1; // DIRECT=0, FEE=1 type RewardClaimTuple = [number, string, string, number]; // [id, address, sum, claimType] type RewardClaimEntry = [string[], RewardClaimTuple]; // [merkleProof[], tuple] function extractClaim( rewardClaims: RewardClaimEntry[], beneficiary: string, claimType: ClaimType, ) { const entry = rewardClaims.find(([, tuple]) => { const [, address, , ct] = tuple; return ( address.toLowerCase() === beneficiary.toLowerCase() && ct === claimType ); }); if (!entry) return null; const [merkleProof, [id, address, sum, ct]] = entry; return { merkleProof, body: { rewardEpochId: BigInt(id), beneficiary: address, amount: BigInt(sum), claimType: BigInt(ct), }, }; } ``` ### 6. Submit the claim transaction Call: `RewardManager.claim(beneficiary, recipient, lastEpochIdToClaim, wrapRewards, claims)` Where: - `beneficiary`: signing policy address (DIRECT) **or** identity address (FEE) - `recipient`: the address that should receive rewards - `claims`: array of the structs from Step 5 - `lastEpochIdToClaim`: set to the **maximum** `body.rewardEpochId` included in `claims` - `wrapRewards`: boolean (`true` unless explicitly set to `false`) Example: ```ts if (claims.length === 0) throw new Error("No claims to submit."); const lastEpochIdToClaim = claims .map((c) => c.body.rewardEpochId) .reduce((max, v) => (v > max ? v : max)); const tx = await rewardManager .connect(signer) .claim(beneficiary, recipient, lastEpochIdToClaim, wrapRewards, claims); console.log("Submitted:", tx.hash); await tx.wait(); console.log("Confirmed:", tx.hash); ``` ### 7. Claim staking rewards :::tip You can also claim staking rewards via the [Flare Portal](https://portal.flare.network/) or using the [flare-tx-sdk](/network/flare-tx-sdk). ::: Instantiate contract clients (ABI + address) for: - [`ValidatorRewardManager`](/network/fsp/solidity-reference) ```ts // 1) Provider + signer const provider = new JsonRpcProvider(process.env.RPC_URL); // Signer must be a self-bond address or an address used for delegating stake. const stakingSigner = new Wallet(process.env.STAKING_PRIVATE_KEY!, provider); // 2) Contract instances (ABI omitted here; use your generated ABI or interface) const validatorRewardManager = new Contract( process.env.VALIDATOR_REWARD_MANAGER_ADDRESS!, VALIDATOR_REWARD_MANAGER_ABI, provider, ); ``` Call: `ValidatorRewardManager.claim(rewardOwner, recipient, rewardAmount, wrapRewards)` Where: - `rewardOwner`: self-bond address or address used for delegating stake - `recipient`: the address that should receive rewards - `rewardAmount`: amount of rewards to be claimed - `wrapRewards`: boolean (`true` unless explicitly set to `false`) Example: ```ts const state = await validatorRewardManager.getStateOfRewards(rewardOwner); let rewardAmount = state[0] - state[1]; const tx = await validatorRewardManager .connect(stakingSigner) .claim(rewardOwner, recipient, rewardAmount, wrapRewards); console.log("Submitted:", tx.hash); await tx.wait(); console.log("Confirmed:", tx.hash); ``` ## Troubleshooting
T0. Authorization failure: executor not allowed or recipient not allowlisted - If the signer sending `RewardManager.claim(...)` is not the beneficiary, ensure it is configured as a **claim executor** via `setClaimExecutors(...)`. - If the transaction reverts when using a `recipient` address, ensure the recipient is allowlisted via `setAllowedClaimRecipients(...)`. - Ensure you set these using the correct controlling account: - **DIRECT**: signing policy account - **FEE**: identity account
T1. No matching tuple found for my address - Double-check: - Beneficiary address selection (DIRECT uses signing policy address; FEE uses identity address). - `claimType` value (DIRECT=0, FEE=1).
T2. Claim transaction reverts - Ensure the Merkle proof and tuple fields (`rewardEpochId`, `beneficiary`, `amount`, `claimType`) are copied exactly from the JSON. - Ensure `lastEpochIdToClaim` is `>=` the maximum `rewardEpochId` included in your `claims` array. - Ensure the epoch is signed (non-zero `rewardsHash`) and within the claimable range you read onchain. If a revert reason indicates additional authorization rules (e.g., the caller must be a specific executor), those rules are enforced by the contract and must be satisfied as written in the revert reason; they are not inferable from the claim flow alone.
--- ## Web2 FDC Setup This guide explains how data providers can set up support for the [Web2 FDC attestation type](/fdc/attestation-types/web2-json). The setup involves configuring a Web2 verifier server and updating the FDC Client. ## Prerequisites - Access to the [FDC Suite Deployment repository](https://github.com/flare-foundation/fdc-suite-deployment). - Access to the [FSP Deployment repository](https://github.com/flare-foundation/flare-systems-deployment). - API key for the Ignite Web2Json source (see [Obtaining an Ignite API Key](#obtaining-an-ignite-api-key)) ## Set Up Web2 Verifier 1. Pull the [FDC Suite Deployment](https://github.com/flare-foundation/fdc-suite-deployment) repository version v1.2.0: ```bash git clone --branch v1.2.0 https://github.com/flare-foundation/fdc-suite-deployment.git cd fdc-suite-deployment ``` 2. Configure environment variables in `.env` (see `.env.example` for reference): ```bash WEB2_IGNITE_API_KEY=your_ignite_api_key_here WEB2_SOURCE_IDS=Ignite VERIFIER_API_KEYS=key1,key2 ``` 3. Run the configuration generator: ```bash ./generate-config.sh ``` 4. Start the Web2 verifier server: ```bash cd web2-verifier/ docker compose up -d ``` 5. (Optional) Specify memory and CPU restrictions in `docker-compose.yaml` if needed. ## Update FDC Client 1. Update your [Flare Systems Protocol Deployment](https://github.com/flare-foundation/flare-systems-deployment) to version v1.2.0: ```bash cd flare-systems-deployment git fetch origin git checkout v1.2.0 ``` 2. Set the verifier address and API key in `.env` (see `.env.example` for reference): ```bash IGNITE_WEB2JSON_URL=http://:9801/verifier/web2/Web2Json/verifyFDC IGNITE_WEB2JSON_API_KEY=key1 ``` Replace `` with the hostname or IP address where your Web2 verifier is running. 3. Regenerate the FDC Client configuration: ```bash ./populate-config.sh ``` 4. Restart the FDC Client to apply the changes: ```bash docker compose up -d ``` This pulls the latest version and recreates the container. ## Obtaining an Ignite API Key API keys for the Ignite proxy are encrypted with each provider's signing policy public key. For detailed instructions on how to obtain and decrypt your API key, see the [FDC Attestation Verifier repository](https://github.com/flare-foundation/verifier-indexer-api/tree/main/scripts/web2#readme). :::tip[What's Next] Once your Web2 FDC setup is complete, explore the FDC developer guides to learn how to use the attestation types: - [FDC Guides](/fdc/guides) - Tutorials for using FDC with Hardhat and Foundry. - [Web2 JSON Attestation Type](/fdc/attestation-types/web2-json) - Learn about the Web2 JSON attestation type. ::: --- ## Flare AI Skills [Flare AI Skills](https://github.com/flare-foundation/flare-ai-skills) is a collection of Agent Skills for Cursor, Claude Code, Codex and other [skills.sh](https://skills.sh/) compatible agents. The skills give your AI assistant structured domain knowledge about Flare. That includes [Flare Time Series Oracle](/ftso/overview), [FAssets](/fassets/overview), [Flare Data Connector](/fdc/overview), and [Smart Accounts](/smart-accounts/overview). You get accurate, context-aware help when building on Flare. ## Available skills | Skill | Description | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | **flare-general** | Flare overview, networks, chain IDs, RPC endpoints, explorers, faucets, and developer tooling | | **flare-ftso** | [FTSO](/ftso/overview) price feeds, feed IDs, on-chain/off-chain consumption, fee calculation, delegation, and scaling anchor feeds | | **flare-fassets** | [FAssets](/fassets/overview) overview, minting, redemption, agents, collateral, and smart contract integration | | **flare-fdc** | [Flare Data Connector](/fdc/overview) overview, attestation types, request flow, Merkle proofs and Data Availability Layer (DAL) verification | | **flare-smart-accounts** | [Smart Accounts](/smart-accounts/overview) overview, account abstraction for XRPL users to interact with Flare without owning FLR | You can install one or more skills depending on what you are building on Flare. ## Installation You can install the skills using the [skills.sh](https://skills.sh/) CLI, which is recommended for general use or in Claude Code. ### Using skills.sh This is the recommended way to install the skills using the [skills.sh](https://skills.sh/) CLI. Many AI agents support `skills.sh`, including Cursor, Claude Code, Codex, and more. From your project or any directory, run the following command to install the skills: ```bash npx skills add https://github.com/flare-foundation/flare-ai-skills ``` It will ask you to select the skills you want to install. Read more about `skills.sh` in the [skills.sh documentation](https://skills.sh/docs). ### Claude Code To install the skills in Claude Code, add the marketplace and install the skills you need. 1. Add the marketplace: ```bash /plugin marketplace add flare-foundation/flare-ai-skills ``` 2. Select skills you need and install them. ```bash /plugin install flare-general@flare-ai-skills /plugin install flare-ftso@flare-ai-skills /plugin install flare-fassets@flare-ai-skills /plugin install flare-fdc@flare-ai-skills /plugin install flare-smart-accounts@flare-ai-skills ``` 3. Run this command to manage plugins in Claude Code: ```bash /plugin ``` Read more about [plugins in Claude Code](https://code.claude.com/docs/en/skills). ## Updating skills To update all installed skills (when using `skills.sh`): ```bash npx skills update ``` To update a single skill, reinstall it: ```bash npx skills add https://github.com/flare-foundation/flare-ai-skills --skill flare-ftso ``` ## Using the skills After installation, use your AI agent as usual and refer to the skill when needed. Example prompts (click the copy button to copy the full prompt): - `Use the flare-general skill and explain how to set up a Hardhat project for Flare development` - `Use the flare-ftso skill and show how to consume FTSO price feeds in a Solidity contract.` - `Use the flare-fassets skill and explain how to mint FXRP step by step` - `Use the flare-fdc skill and show how to request an EVMTransaction attestation and verify it in a contract` - `Use the flare-smart-accounts skill and show how to deposit to a vault` The agent will use the workflow and concepts from the Flare AI Skills `SKILL.md`. It can reference Flare Developer Hub links from the reference files. :::tip[What's next] [Deploy your first contract](/network/getting-started) to get started on Flare. Or explore [Developer tools](/network/developer-tools) for RPCs, wallet SDKs, bridges, indexers, and more. ::: --- ## Retrieving Contract Addresses export const contracts = ["FlareContractRegistry"]; ## Overview Flare provides a registry of enshrined protocol contracts such as `FtsoV2`, `FdcHub`, and `RandomNumberV2`. To ensure reliability, these contract addresses should **always be retrieved dynamically** via the **Flare Contract Registry** rather than hardcoding them. Using the registry ensures your contracts, and dApps are resistant to future upgrades and cannot be misled by offchain or unverified sources. ## Flare Contract Registry Address The [`FlareContractRegistry`](/network/solidity-reference/IFlareContractRegistry) smart contract is the **only trusted source** for resolving official protocol contract addresses. The registry is deployed at the same address across all Flare networks: ## Retrieval Methods In the following code example, the `RandomNumberFetcher` contract retrieves the address of `RandomNumberV2` using the three approaches described below. {RandomNumberFetcher} {/* prettier-ignore */} Open in Remix ### 🔴 Hardcoded Address The `getRandomNumberHardcoded` function below uses a hardcoded address to create an interface instance. Not recommended in production. :::warning - Never hardcode contract addresses in production. - Avoid using contract addresses obtained from unofficial sources like DMs, social media, or third-party websites. - Instead of passing contract addresses directly into constructors, retrieve them dynamically, as shown below. ::: ### Flare Contract Registry Smart Contract The Flare Contract Registry smart contract exposes [`getContractAddressByName`](/network/solidity-reference/IFlareContractRegistry#getcontractaddressbyname) or [`getContractAddressByHash`](/network/solidity-reference/IFlareContractRegistry#getcontractaddressbyhash) to retrieve the address of the contract. In the code example above, the `getRandomNumberViaRegistryName` function uses the `getContractAddressByName` method to retrieve the address of `RandomNumberV2`. ### Contract Registry Library The [Flare periphery package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package includes a helper utility [`ContractRegistry`](https://github.com/flare-foundation/flare-solidity-periphery-package-mirror/blob/master/flare/ContractRegistry.sol). This library provides shorthand functions for accessing common protocol contracts, e.g., `ContractRegistry.getRandomNumberV2()`. In the example, the `getRandomNumberViaContractLibrary` function demonstrates this approach. :::tip If the contract you are looking for does not have a shorthand method in the `ContractRegistry` library, you can fall back to using `getContractAddressByName` or call `getAllContracts` to list all registered contracts. ::: --- ## Flare Developer Hub MCP Server The Flare Developer Hub MCP server exposes the full Flare documentation to any [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) compatible AI tool. Once connected, your AI assistant can search and fetch accurate, up-to-date Flare documentation directly — no copy-pasting, no outdated context. The server is hosted at `https://dev.flare.network/mcp` and is available to anyone with an MCP-compatible AI client. ## Available tools The server exposes the following tools: - `docs_search` - full-text search across all Flare Developer Hub documentation pages. - `docs_fetch` - fetch the full content of a specific Flare Developer Hub documentation page. ## Setup ### Quick connect The fastest way to connect is directly from any page on the Flare Developer Hub. Click the **Copy page** button in the top-right corner and choose one of the MCP options from the dropdown: - **Copy MCP Server** — copies the MCP server URL to your clipboard. - **Connect to Cursor** — opens Cursor and installs the MCP server automatically. - **Connect to VS Code** — opens VS Code and installs the MCP server automatically. ### Claude Code Run the following command to add the Flare MCP server to Claude Code: ```bash claude mcp add --transport http flare-devhub https://dev.flare.network/mcp ``` To verify the server is connected: ```bash claude mcp list ``` ### Cursor Add it directly to your Cursor MCP config file at `~/.cursor/mcp.json`: ```json { "mcpServers": { "flare-devhub": { "url": "https://dev.flare.network/mcp" } } } ``` ### Other MCP-compatible tools For any tool that supports the [MCP Streamable HTTP transport](https://modelcontextprotocol.io/docs/concepts/transports), use the following endpoint: ``` https://dev.flare.network/mcp ``` Refer to your tool's documentation for instructions on adding a remote MCP server. ## Example prompts After connecting the server, your AI assistant can answer Flare-specific questions by searching and fetching documentation on demand. - `Search the Flare docs and explain how FTSO price feeds work` - `Fetch the Flare FDC getting started page and show me how to request an EVMTransaction attestation` - `Search the Flare docs for FAssets minting and summarize the steps` - `Look up the Flare network RPC endpoints from the developer hub` - `Search the Flare docs and explain how Smart Accounts work for XRPL users` ## Video walkthrough :::tip[What's next] Explore [Flare AI Skills](/network/guides/flare-ai-skills) for structured domain knowledge that works alongside the MCP server. ::: --- ## Flare for Go Devs This guide is for developers who want to interact with Flare using Go. In this guide, using Go, you will learn how to: - Query a contract on Flare using the Go API for [Geth](https://github.com/ethereum/go-ethereum), a client that implements the full Ethereum JSON-RPC API. - Compile a Solidity contract using the CLI interface of [solc](https://github.com/argotorg/solidity), the Solidity compiler. - Deploy your compiled contract on Flare. :::tip All examples in this guide are available at [developer-hub/examples](https://github.com/flare-foundation/developer-hub/tree/main/examples). ::: ## Getting started Install Geth by following the instructions in the [Geth documentation](https://geth.ethereum.org/docs/getting-started/installing-geth). Also install the Solidity compiler by following the instructions in the [Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html#linux-packages). The main commands are provided here: ```bash brew tap ethereum/ethereum brew install ethereum solidity ``` ```bash sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt update sudo apt install ethereum solc ``` Add the `ethclient` and `keystore` packages to your Go project: ```bash go get github.com/ethereum/go-ethereum/ethclient go get github.com/ethereum/go-ethereum/accounts/keystore ``` The folder structure of your Go project should look like: ```plaintext developer-hub-go/ ├── coston2/ │ └── *.go ├── flare/ │ └── *.go ├── main.go ├── go.mod └── go.sum ``` ### Usage You need to connect to testnet or mainnet via an RPC, any RPC listed on the [Network Configuration](/network/overview#configuration) page will work. For this guide, you can use the Public RPC. {ChainIdCoston2} {ChainIdFlare} ## Querying a contract To query a contract, two pieces of information are required: - Contract address - Contract ABI (Application Binary Interface) For this example, you can use the `FlareContractRegistry` contract which has the same address `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` across both testnet and mainnet. ### Fetch ABI To fetch a contract's ABI, copy the FlareContractRegistry ABI from the appropriate Routescan endpoint and paste it into `FlareContractRegistry.abi` next to your `go.mod`. The `FlareContractRegistry` itself is deployed at the same `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` on every Flare-family network — pick the URL that matches the chain you'll deploy to: - **Flare Testnet Coston2 (chain id 114):** [routescan URL](https://api.routescan.io/v2/network/testnet/evm/114/etherscan/api?module=contract&action=getabi&address=0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019&format=raw) - **Flare Mainnet (chain id 14):** [routescan URL](https://api.routescan.io/v2/network/mainnet/evm/14/etherscan/api?module=contract&action=getabi&address=0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019&format=raw) To generate the ABI bindings, which will be saved to `FlareContractRegistry.go`: ```bash abigen --abi FlareContractRegistry.abi --pkg coston2 --type FlareContractRegistry --out coston2/FlareContractRegistry.go ``` ```bash abigen --abi FlareContractRegistry.abi --pkg flare --type FlareContractRegistry --out flare/FlareContractRegistry.go ``` ### Make query You can now query the `FlareContractRegistry` contract to get the addresses of other Flare contracts. For example, querying it for the address of the `WNat` contract: {MakeQueryCoston2} {MakeQueryFlare} ## Compiling with solc For this example, you can use the `FtsoV2FeedConsumer` contract to query the FTSOv2 feeds. Copy the `FtsoV2FeedConsumer` sample contract code given below, and save the `.sol` file in the same folder as your `go.mod`.
`FtsoV2FeedConsumer` sample contract Note that the contract interface dependencies have been inlined to avoid any import issues. {FtsoV2FeedConsumerNoImports}
To compile the contract, use the Solidity compiler: ```bash solc --evm-version cancun --abi --bin FtsoV2FeedConsumer.sol -o build ``` The compiled contract will be saved in the `build/` folder. ## Create account Before deploying a contract, you need an account. You can create an account using the following code: {CreateAccount} This will generate a new account and save the keystore file in the current directory. The account address will be printed to the console. :::danger - Never share your private keys. - Never put your private keys in source code. - Never commit private keys to a Git repository. ::: - For testnet, you can get free testnet C2FLR on the [Coston2 Faucet](https://faucet.flare.network/coston2). - For mainnet you will need FLR. ## Deploying with geth To deploy the contract, you need to first generate the contract bindings: ```bash abigen --bin=build/FtsoV2FeedConsumer.bin --abi=build/FtsoV2FeedConsumer.abi --pkg coston2 --type FtsoV2FeedConsumer --out coston2/FtsoV2FeedConsumer.go ``` ```bash abigen --bin=build/FtsoV2FeedConsumer.bin --abi=build/FtsoV2FeedConsumer.abi --pkg flare --type FtsoV2FeedConsumer --out flare/FtsoV2FeedConsumer.go ``` This will generate the `FtsoV2FeedConsumer.go` file, with the function `DeployFtsoV2FeedConsumer`. Copy the contents of the generated keystore file into the `key` constant in the following code: {DeployContractCoston2} {DeployContractFlare} Congratulations! You have now successfully deployed a contract on Flare using Go. :::tip[What's next?] Learn how to interact with Flare's enshrined oracle [FTSOv2 using Go](/ftso/guides/read-feeds-offchain). ::: --- ## Flare for JavaScript Devs This guide is for developers who want to interact with Flare using JavaScript. In this guide, using JavaScript, you will learn how to: - Query a contract on Flare using [web3.js](https://github.com/web3/web3.js), an async/sync library for interacting with Ethereum-like chains. - Compile a Solidity contract using the CLI interface of [solc](https://github.com/argotorg/solidity), the Solidity compiler. - Deploy your compiled contract on Flare. :::tip All examples in this guide are available at [developer-hub/examples](https://github.com/flare-foundation/developer-hub/tree/main/examples). ::: ## Getting started Install the Solidity compiler by following the instructions in the [Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html#linux-packages). The main commands are provided here: ```bash brew tap ethereum/ethereum brew install solidity ``` ```bash sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt update sudo apt install solc ``` Install web3.js using either `npm` or `yarn`: ```bash npm install web3 ``` ```bash yarn add web3 ``` ### Usage You need to connect to testnet or mainnet via an RPC, any RPC listed on the [Network Configuration](/network/overview#configuration) page will work. For this guide, you can use the Public RPC. {ChainIdCoston2} {ChainIdFlare} ## Querying a contract To query a contract, two pieces of information are required: - Contract address - Contract ABI (Application Binary Interface) For this example, you can query the `FlareContractRegistry` contract which has the same address `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` across both testnet and mainnet. ### Fetch ABI To fetch a contract's ABI programmatically, you can query the [Flare Blockchain Explorer API](https://flare-explorer.flare.network/api-docs): {FetchAbiCoston2} {FetchAbiFlare} ### Make query You can now query the `FlareContractRegistry` contract to get the addresses of other Flare contracts. For example, querying it for the address of the `WNat` contract: {MakeQueryCoston2} {MakeQueryFlare} ## Compiling with solc For this example, you can use the `FtsoV2FeedConsumer` contract to query the FTSOv2 feeds. Copy the `FtsoV2FeedConsumer` sample contract code given below, and save the `.sol` file in the same folder as your `package.json`.
`FtsoV2FeedConsumer` sample contract Note that the contract interface dependencies have been inlined to avoid any import issues. {FtsoV2FeedConsumerNoImports}
```bash solc --evm-version cancun FtsoV2FeedConsumer.sol --abi --bin -o build ``` This will generate two files `build/FtsoV2FeedConsumer.abi` and `build/FtsoV2FeedConsumer.bin` files with the contract's ABI and bytecode. Rename `FtsoV2FeedConsumer.abi` to `FtsoV2FeedConsumer.json`. ## Create account Before deploying a contract, you need to have an account with some testnet or mainnet gas tokens. You can create a new Flare account using `createAccount.js`: {CreateAccount} This will output a new private key and an account pair. :::danger - Never share your private keys. - Never put your private keys in source code. - Never commit private keys to a Git repository. ::: You can save the account and private key into environment variables `ACCOUNT` and `ACCOUNT_PRIVATE_KEY` respectively. :::note You can also import the raw hex private key to MetaMask and any other wallet - the private key can be shared between your JavaScript code and any number of wallets. ::: - For testnet, you can get free testnet C2FLR on the [Coston2 Faucet](https://faucet.flare.network/coston2). - For mainnet you will need FLR. ## Deploying with web3.js With the account ready, you can now deploy the contract. In a `deployContract.js` file, you can define the following code to deploy the contract: {DeployContractCoston2} {DeployContractFlare} You can now run the `deployContract.js` script to deploy the contract. The contract address will be printed once the deployment is successful. You can check the contract address on a Flare Blockchain Explorer, linked on the [Network Configuration](/network/overview#configuration) page. Congratulations! You have now successfully deployed a contract on Flare using JavaScript. :::tip[What's next?] Learn how to interact with Flare's enshrined oracle [FTSOv2 using JavaScript](/ftso/guides/read-feeds-offchain). ::: --- ## Flare for Python Devs This guide is for developers who want to interact with Flare using Python. In this guide, using Python, you will learn how to: - Query a contract on Flare using [web3.py](https://github.com/ethereum/web3.py), an async/sync library for interacting with Ethereum-like chains. - Compile a Solidity contract using [py-solc-x](https://github.com/ApeWorX/py-solc-x), a wrapper around the Solidity compiler. - Deploy your compiled contract on Flare. :::tip All examples in this guide are available at [developer-hub/examples](https://github.com/flare-foundation/developer-hub/tree/main/examples). ::: ## Getting started ```bash uv add web3 py-solc-x ``` ```bash pip install web3 py-solc-x ``` ### Usage You need to connect to testnet or mainnet via an RPC, any RPC listed on the [Network Configuration](/network/overview#configuration) page will work. For this guide, you can use the Public RPC. {ChainIdCoston2} {ChainIdFlare} ## Querying a contract To query a contract, two pieces of information are required: - Contract address - Contract ABI (Application Binary Interface) For this example, you can query the `FlareContractRegistry` contract which has the same address `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` across both testnet and mainnet. ### Fetch ABI To fetch a contract's ABI programmatically, you can query the [Flare Blockchain Explorer API](https://flare-explorer.flare.network/api-docs): {FetchAbiCoston2} {FetchAbiFlare} ### Make query You can now query the `FlareContractRegistry` contract to get the addresses of other Flare contracts. For example, querying it for the address of the `WNat` contract: {MakeQueryCoston2} {MakeQueryFlare} ## Compiling a contract For this example, you can use the `FtsoV2FeedConsumer` contract to query the FTSOv2 feeds. Copy the `FtsoV2FeedConsumer` sample contract code given below, and save the `.sol` file in the same folder as your Python script.
`FtsoV2FeedConsumer` sample contract Note that the contract interface dependencies have been inlined to avoid any import issues. {FtsoV2FeedConsumerNoImports}
### Set up utils To compile the contracts, you need to first set up some basic helper functions to read the `.sol` file and write the compiled output to a `.json` file. In a `utils.py` file, you can define the following functions: {Utils} ### Compile with py-solc-x With these functions ready, you can now compile the contract. In a `compile_contract.py` file, you can define the following code to install the correct version of the Solidity compiler and compile the contract: {CompileContract} You can now run the `compile_contract.py` script to compile the contract. The compiled output will be saved to `FtsoV2FeedConsumer.json`. ```bash uv run compile_contract.py ``` ```bash python compile_contract.py ``` ## Create account Before deploying a contract, you need to have an account with some testnet or mainnet gas tokens. You can create a new Flare account from the CLI: {CreateAccount} This will output a new private key and an account pair. :::danger - Never share your private keys. - Never put your private keys in source code. - Never commit private keys to a Git repository. ::: You can save the account and private key into environment variables `ACCOUNT` and `ACCOUNT_PRIVATE_KEY` respectively. ```bash export ACCOUNT=
export ACCOUNT_PRIVATE_KEY= ``` :::note You can also import the raw hex private key to MetaMask and any other wallet - the private key can be shared between your Python code and any number of wallets. ::: - For testnet, you can get free testnet C2FLR on the [Coston2 Faucet](https://faucet.flare.network/coston2). - For mainnet you will need FLR. ## Deploying with web3.py With the functions and account ready, you can now deploy the contract. In a `deploy_contract.py` file, you can define the following code to deploy the contract: {DeployContractCoston2} {DeployContractFlare} You can now run the `deploy_contract.py` script to deploy the contract. The contract address will be printed once the deployment is successful. You can check the contract address on a Flare Blockchain Explorer, linked on the [Network Configuration](/network/overview#configuration) page. ```bash uv run deploy_contract.py ``` ```bash python deploy_contract.py ``` Congratulations! You have now successfully deployed a contract on Flare using Python 🐍. :::tip[What's next?] Learn how to interact with Flare's enshrined oracle [FTSOv2 using Python](/ftso/guides/read-feeds-offchain). ::: --- ## Flare for React Devs This guide is for React developers who want to interact with Flare using [Wagmi](https://wagmi.sh/) and the [`@flarenetwork/flare-wagmi-periphery-package`](https://www.npmjs.com/package/@flarenetwork/flare-wagmi-periphery-package). The package provides two ways to read and write Flare contracts: - **Generic hooks:** use Wagmi's [`useReadContract`](https://wagmi.sh/react/api/hooks/useReadContract) with typed ABIs imported from the Flare Wagmi Periphery package. - **Contract-specific hooks:** use auto-generated hooks like `useReadIFlareContractRegistry` that already know the ABI. In this guide, you will set up a React project, configure Wagmi for Flare, and query the [`WNat`](/network/solidity-reference/IWNat) contract address from the [`FlareContractRegistry`](/network/solidity-reference/IFlareContractRegistry) using both approaches. ## Prerequisites - [Node.js](https://nodejs.org/) (v18 or later) - `npm` package manager ## Setting up the project Scaffold a new React + TypeScript project using [Vite](https://vite.dev/): ```bash npm create vite@latest my-flare-app -- --template react-ts cd my-flare-app ``` Install the Wagmi, Viem, TanStack Query, and the [Flare Wagmi periphery package](https://www.npmjs.com/package/@flarenetwork/flare-wagmi-periphery-package): ```bash npm install wagmi viem @tanstack/react-query @flarenetwork/flare-wagmi-periphery-package ``` ## Configure Wagmi for Flare Create a `wagmi.config.ts` file in the `src` directory to define the Wagmi configuration with Flare networks: ```typescript title="wagmi.config.ts" export const config = createConfig({ chains: [flare, flareTestnet], connectors: [injected()], transports: { [flare.id]: http(), [flareTestnet.id]: http(), }, }); ``` Wrap your application with the required providers in `src/main.tsx`: ```typescript title="src/main.tsx" const queryClient = new QueryClient(); createRoot(document.getElementById("root")!).render( , ); ``` ## Query contract data The example below queries the [`FlareContractRegistry`](/network/solidity-reference/IFlareContractRegistry) to look up the [`WNat`](/network/solidity-reference/IWNat) contract address. It demonstrates both approaches to query the contract data: - **Generic hooks:** use Wagmi's [`useReadContract`](https://wagmi.sh/react/api/hooks/useReadContract) with typed ABIs imported from the Flare Wagmi Periphery package. - **Contract-specific hooks:** use auto-generated hooks like `useReadIFlareContractRegistry` that already know the ABI. ```typescript title="src/WNatQuery.tsx" useReadIFlareContractRegistry, iFlareContractRegistryAbi, } from "@flarenetwork/flare-wagmi-periphery-package/contracts/flare"; // The Flare Contract Registry address const FLARE_CONTRACTS_REGISTRY = "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019" as const; export function WNatQuery() { // Generic useReadContract with ABI const { data: wNatAddressGeneric, isLoading: isLoadingGeneric, error: errorGeneric, } = useReadContract({ address: FLARE_CONTRACTS_REGISTRY, abi: iFlareContractRegistryAbi, functionName: "getContractAddressByName", args: ["WNat"], }); // Contract-specific hook const { data: wNatAddressSpecific, isLoading: isLoadingSpecific, error: errorSpecific, } = useReadIFlareContractRegistry({ address: FLARE_CONTRACTS_REGISTRY, functionName: "getContractAddressByName", args: ["WNat"], }); return ( WNat Contract Query Generic Hook with ABI Uses Wagmi's generic useReadContract hook with{" "} iFlareContractRegistryAbi imported from the package. {isLoadingGeneric && Loading...} {errorGeneric && Error: {errorGeneric.message}} {wNatAddressGeneric && {wNatAddressGeneric as string}} Contract-Specific Hook Uses useReadIFlareContractRegistry, a pre-typed hook generated for the contract. No ABI import needed. {isLoadingSpecific && Loading...} {errorSpecific && Error: {errorSpecific.message}} {wNatAddressSpecific && {wNatAddressSpecific as string}} ); } ``` ## Run the app Update `src/App.tsx` to render the `WNatQuery` component: ```typescript title="src/App.tsx" function App() { return ( ); } export default App; ``` Start the development server: ```bash npm run dev ``` Open the URL shown in the terminal. The app will query the [`FlareContractRegistry`](/network/solidity-reference/IFlareContractRegistry) and display the [`WNat`](/network/solidity-reference/IWNat) contract address using both approaches. ### Generic hooks The generic approach uses the [`useReadContract`](https://wagmi.sh/react/api/hooks/useReadContract) hook from Wagmi. You import the typed ABI (`iFlareContractRegistryAbi`) from the package and pass it as the `abi` prop. This gives you complete type safety for `functionName` and `args`. ```typescript const { data } = useReadContract({ address: FLARE_CONTRACTS_REGISTRY, abi: iFlareContractRegistryAbi, functionName: "getContractAddressByName", args: ["WNat"], }); ``` ### Contract-specific hooks The package also exports auto-generated hooks named after each contract. Instead of passing an `abi` prop, you call the contract-specific hook directly. The hook already knows the ABI, so you only need to provide `address`, `functionName`, and `args`. ```typescript const { data } = useReadIFlareContractRegistry({ address: FLARE_CONTRACTS_REGISTRY, functionName: "getContractAddressByName", args: ["WNat"], }); ``` ## Choosing an approach | | Generic hooks | Contract-specific hooks | | --------------- | ---------------------------------------------------------------- | ------------------------------------------------- | | **Import** | `useReadContract` from Wagmi + ABI from package | `useReadIFlareContractRegistry` etc. from package | | **ABI prop** | Required — pass the imported ABI | Not needed — baked into the hook | | **Type safety** | Full, via ABI generic | Full, via generated hook signature | | **Best for** | Mixing contracts in one component, using ABIs with viem directly | Concise code when working with a single contract | ## Available exports The package provides you with typed ABIs and contract-specific hooks for all Flare periphery contracts, organized by network. To work with [Flare Time Series Oracle (FTSO)](/ftso/overview) contracts, you can import them using both approaches in the following ways: ```typescript // Generic: import ABIs for use with useReadContract / useWriteContract // Contract-specific: import auto-generated hooks useReadIFtso, useWriteIiFtso, } from "@flarenetwork/flare-wagmi-periphery-package/contracts/"; ``` Where `` is one of: - `flare` - `songbird` - `coston` - `coston2`. :::tip[What's next?] - Explore the [Network Configuration](/network/overview#configuration) for RPC endpoints and chain details. - Learn how to use the [Hardhat & Foundry Starter Kit](/network/guides/hardhat-foundry-starter-kit) to deploy and interact with Flare contracts. ::: --- ## Flare for Rust Devs This guide is for developers who want to interact with Flare using Rust. In this guide, using Rust, you will learn how to: - Query a contract on Flare using [alloy-rs](https://github.com/alloy-rs), an async library for interacting with Ethereum-like chains. - Compile a Solidity contract using the CLI interface of [solc](https://github.com/argotorg/solidity), the Solidity compiler. - Deploy your compiled contract on Flare. :::tip All examples in this guide are available at [developer-hub/examples](https://github.com/flare-foundation/developer-hub/tree/main/examples). ::: ## Getting started Install the Solidity compiler by following the instructions in the [Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html#linux-packages). The main commands are provided here: ```bash brew tap ethereum/ethereum brew install solidity ``` ```bash sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt update sudo apt install solc ``` Install the following dependencies: ```bash cargo add alloy eyre tokio --features alloy/full,tokio/rt,tokio/rt-multi-thread,tokio/macros ``` ### Usage You need to connect to testnet or mainnet via an RPC, any RPC listed on the [Network Configuration](/network/overview#configuration) page will work. For this guide, you can use the Public RPC. {ChainIdCoston2} {ChainIdFlare} ```bash cargo run --bin chain_id ``` ## Querying a contract To query a contract, two pieces of information are required: - Contract address - Contract ABI (Application Binary Interface) For this example, you can use the `FlareContractRegistry` contract which has the same address `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` across both testnet and mainnet. ### Fetch ABI To fetch a contract's ABI, copy the FlareContractRegistry ABI from the appropriate Routescan endpoint and paste it into `FlareContractRegistry.json` next to your `Cargo.toml`. Pick the URL that matches the chain you'll deploy to: - **Flare Testnet Coston2 (chain id 114):** [routescan URL](https://api.routescan.io/v2/network/testnet/evm/114/etherscan/api?module=contract&action=getabi&address=0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019&format=raw) - **Flare Mainnet (chain id 14):** [routescan URL](https://api.routescan.io/v2/network/mainnet/evm/14/etherscan/api?module=contract&action=getabi&address=0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019&format=raw) ### Make query You can now query the `FlareContractRegistry` contract to get the addresses of other Flare contracts. For example, querying it for the address of the `WNat` contract: {MakeQueryCoston2} {MakeQueryFlare} ```bash cargo run --bin make_query ``` ## Compiling a contract For this example, you can use the `FtsoV2FeedConsumer` contract to query the FTSOv2 feeds. Copy the `FtsoV2FeedConsumer` sample contract code given below, and save the `.sol` file in the same folder as your `Cargo.toml`.
`FtsoV2FeedConsumer` sample contract Note that the contract interface dependencies have been inlined to avoid any import issues. {FtsoV2FeedConsumerNoImports}
### Compile with solc To compile the contract using the Solidity CLI compiler, create a file named `config.json`: {JSON.stringify(Config, null, 2)} ```bash solc --standard-json config.json > FtsoV2FeedConsumer.json ``` This will generate a `FtsoV2FeedConsumer.json` file with the contract's ABI and bytecode. ### Modify format Things get a bit annoying here, as the alloy-rs `solc!()` macro expects a specific format for the JSON, which is not the same as the output from the Solidity compiler. To fix this, in the generated `FtsoV2FeedConsumer.json`, remove the top-level JSON fields, after you are done, the JSON should look like: ```json title="FtsoV2FeedConsumer.json" { "abi": [ ... ], "evm": { ... } "metadata": "..." } ``` ## Create account Before deploying a contract, you need to have an account with some testnet or mainnet gas tokens. You can create a new Flare account using `create_account.rs`: {CreateAccount} ```bash cargo run --bin create_account ``` This will output a new private key and an account pair. :::danger - Never share your private keys. - Never put your private keys in source code. - Never commit private keys to a Git repository. ::: You can save the account and private key into environment variables `ACCOUNT` and `ACCOUNT_PRIVATE_KEY` respectively. :::note You can also import the raw hex private key to MetaMask and any other wallet - the private key can be shared between your Rust code and any number of wallets. ::: - For testnet, you can get free testnet C2FLR on the [Coston2 Faucet](https://faucet.flare.network/coston2). - For mainnet you will need FLR. ## Deploying with alloy-rs With the account ready, you can now deploy the contract. In a `deploy_contract.rs` file, you can define the following code to deploy the contract: {DeployContractCoston2} {DeployContractFlare} You can now run the `deploy_contract.rs` script to deploy the contract. The contract address will be printed once the deployment is successful. You can check the contract address on a Flare Blockchain explorer, linked on the [Network Configuration](/network/overview#configuration) page. ```bash cargo run --bin deploy_contract ``` Congratulations! You have now successfully deployed a contract on Flare using 🦀. :::tip[What's next?] Learn how to interact with Flare's enshrined oracle [FTSOv2 using Rust](/ftso/guides/read-feeds-offchain). ::: --- ## Gasless USD₮0 Transfers Flare's USD₮0 integration enables native, gasless USDT transfers (also known as meta-transactions) on the Flare network, allowing your end-users to avoid paying gas fees directly. In this guide, you will build a system for gasless USD₮0 transfers: 1. A **frontend application** where users authorize token transfers by signing a message, without needing to submit an onchain transaction themselves. 2. A **backend relayer service** that takes this signed authorization and submits the actual transaction to the Flare network, covering the gas fees on the user's behalf. This powerful pattern significantly enhances user experience by abstracting away the complexities and costs of network gas fees. :::tip[Beyond USD₮0] This guide uses USD₮0 as the primary example, but the underlying principles and a similar implementation logic can be applied to other ERC-20 tokens on Flare that support EIP-3009. ::: ## Meta-transactions Meta-transactions separate the authorization of an action (by the user) from its execution (by a third-party relayer). This is key to enabling gasless experiences. Two Ethereum Improvement Proposals (EIPs) are central to this implementation: ### EIP-712: Typed Structured Data Hashing and Signing [EIP-712](https://eips.ethereum.org/EIPS/eip-712) standardizes the way structured data is hashed and signed. Instead of signing an obscure hexadecimal string, users are presented with a human-readable message in their wallets, detailing what they are authorizing. This ensures transparent offchain signing. ### EIP-3009: Transfer with Authorization [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) extends the ERC-20 token standard to include support for meta-transactions. It allows a token holder to sign an authorization message offchain, which can then be relayed by another account (the relayer) to execute the transfer onchain. The relayer pays the gas fees for this onchain execution. EIP-3009 introduces two key functions, including: - **`transferWithAuthorization`**: This function moves tokens from the `from` address (the authorizer) to the `to` address. It only executes if the provided signature `(v, r, s)` correctly matches the `from` address for the given payload, the current blockchain time is within the `validAfter` and `validBefore` timestamps, and the unique `nonce` has not been previously used by the `from` address for this contract. ```solidity transferWithAuthorization( address from, // Payer's address (Authorizer) address to, // Payee's address uint256 value, // Amount to be transferred uint256 validAfter, // The time after which this is valid (unix time) uint256 validBefore, // The time before which this is valid (unix time) uint256 nonce, // Unique nonce uint8 v, // v of the signature bytes32 r, // r of the signature bytes32 s // s of the signature ) external; ``` - **`receiveWithAuthorization`**: Similar to `transferWithAuthorization`, this function allows a designated party (often the recipient or a relayer) to "pull" tokens from the authorizer's account. This can be useful for scenarios like collecting fees upon receipt of services. (The implementation details are analogous to `transferWithAuthorization`). Both functions incorporate **timestamps** (`validAfter`, `validBefore`) to prevent stale authorizations from being executed indefinitely and a **nonce** (a number used once) to protect against replay attacks, ensuring a signed message can only be submitted once. ## Prerequisites Before you begin, ensure you have the following: - An **EVM compatible wallet** (e.g., Metamask). You can find suitable options on the [Flare Wallets](https://flare.network/wallets) page. - A **Relayer Account:** An EOA on Flare Mainnet, funded with sufficient FLR to cover the gas costs of relaying transactions. - **Development Environment:** - [Node.js](https://nodejs.org/en) (v18 or later) - A package manager (`npm` or `yarn`) - A [React](https://react.dev/) frontend setup, preferably using [Vite](https://vite.dev/) for quick project scaffolding. - **USD₮0 Contract Details:** - Official USD₮0 contract address (`TetherTokenOFTExtension`) for Flare Mainnet. Always refer to the [official USD₮0 documentation](https://docs.usdt0.to/technical-documentation/developer#flare) for the latest addresses. - The ABI for the USD₮0 contract. For this guide, we only need the `name` and`transferWithAuthorization` functions.
Relevant portion of USD₮0 contract ABI {JSON.stringify(USD0, null, 2)}
## Implementation Now, let's build the two main components of our gasless transfer system: the backend relayer and the frontend application. ### Build the Relayer service The relayer is a Node.js Express service responsible for submitting the user's signed authorization to the blockchain. 1. Create a `.env` file in your relayer project's root directory. These variables configure the relayer's connection to Flare Mainnet and its operational parameters. ```dotenv FLARE_RPC_URL=https://flare-api.flare.network/ext/C/rpc # RPC for Flare Mainnet USD0_ADDRESS=0xe7cd86e13AC4309349F30B3435a9d337750fC82D # USD₮0 token contract RELAYER_PRIVATE_KEY=0x...abc # Relayer's private key funded with FLR PORT=3000 # Port to listen on ``` 2. Develop your relayer script (`Relayer.ts`). This script will: 1. **Connect** to Flare using `JsonRpcProvider`, create a `Wallet` from `RELAYER_PRIVATE_KEY`, and instantiate the USD₮0 contract with that wallet. 2. **Spin up an Express server** with CORS and JSON parsing. 3. **Expose a health-check** at `GET /` to confirm the service is running. 4. **Implement** `POST /relay-transfer`, which: - Destructures `{ payload, v, r, s }` from the request body. - Calls `usd0.transferWithAuthorization(...)`, passing the six payload fields plus `v r s`, and sets an explicit `gasLimit` of `120_000`. - Waits for the transaction to mine, then returns `{ txHash }`; on error it returns `{ error }`. 5. **Start listening** on `PORT` (default `3000`) and log a success message. Here's an example implementation: {GaslessRelayer} 3. Install dependencies and run the Relayer: ```bash # Install dependencies (example) # npm install express ethers cors dotenv # npm install -D typescript tsx @types/express @types/cors # Run the relayer npx tsx Relayer.ts ``` You should see a log message indicating the relayer is listening on the specified port. This service must be running for the frontend to successfully relay meta-transactions. ### Build the frontend The frontend allows users to authorize transfers without directly paying gas. It uses React and Vite. 1. In your frontend project's root directory, create a `.env` file. Vite exposes these variables to your app via `import.meta.env`: ```dotenv VITE_USD0_ADDRESS=0xe7cd86e13AC4309349F30B3435a9d337750fC82D # the onchain USD₮0 token contract VITE_RELAYER_URL=http://localhost:3000 # your relayer endpoint ``` 2. Develop the frontend component (`App.tsx`). The main application component will handle: 1. **Connect the wallet** (`window.ethereum`) and request an account. 2. **Instantiate** an `ethers.BrowserProvider` and `Signer`. 3. **Fetch** the token's EIP-712 domain (name, version, chainId, contract). 4. **Define** the `TransferWithAuthorization` typed-data fields. 5. **Build** the payload object - serializing `value` to a string so it JSON-encodes correctly, and setting a one-hour validity window plus a fresh 32-byte nonce. 6. **Call** `signer.signTypedData(domain, types, message)` to pop the wallet and produce a signature. 7. **Extract** `(v, r, s)` from that signature. 8. **POST** the JSON payload plus `(v, r, s)` to your relayer at `${VITE_RELAYER_URL}/relay-transfer`. Here's an example implementation: {GaslessApp} 3. Install dependencies and run the frontend: ```bash # Install dependencies (example, if starting a new Vite + React + TS project) # npm create vite@latest my-gasless-app -- --template react-ts # cd my-gasless-app # npm install ethers # Run the frontend development server npm run dev ``` Vite will typically start the app and open it in your browser. ## Run the app 1. **Start the Relayer Service:** Navigate to your relayer's project directory in a terminal and run `npx tsx Relayer.ts` (or your configured start script). Confirm it's listening. 2. **Start the Frontend:** Open another terminal, navigate to your frontend's project directory, and run `npm run dev`. 3. **Interact with the frontend:** 1. Open the frontend application in your web browser and connect your EVM wallet (ensure it's set to Flare Mainnet). 2. Enter a recipient address and the amount of USD₮0 to transfer. 3. Click **Send Gasless**. Your wallet will prompt you for a signature (this is offchain and gas-free for you). 4. Once signed, the frontend sends the authorization to the relayer, which then submits the actual transaction to Flare Mainnet, paying the gas fee. 5. Observe the feedback from the application for transaction status, and check the transaction on [Flare Explorer](https://flare-explorer.flare.network). Congratulations! You've now implemented a foundational system for gasless USD₮0 transfers on Flare Mainnet. This approach leverages EIP-712 and EIP-3009 to create a significantly improved user experience by abstracting away gas fees. ## Further enhancements For a production-ready application, consider these further enhancements: - **Robust Error Handling:** Implement comprehensive error handling and user-friendly feedback mechanisms in both the frontend and relayer. - **Input Validation & Security:** Add thorough validation for all inputs to the relayer to prevent abuse and ensure data integrity. Implement security best practices (e.g., rate limiting, authentication if needed). - **Dynamic Gas Strategy:** Instead of a fixed gas limit, the relayer could dynamically estimate gas prices and limits for transactions to optimize costs and improve reliability. - **Transaction Monitoring:** Provide users with clear status updates on their relayed transactions. - **Nonce Management:** While EIP-3009 handles nonce checking at the contract level, your relayer might benefit from its own nonce tracking or management for specific users if it needs to handle multiple pending transactions for the same user. --- ## Hardhat and Foundry Starter Kit The starter kit includes a basic setup for configuring Hardhat and Foundry, along with examples on how to compile, test and deploy smart contracts on Flare. ## Prerequisites If you're new to Hardhat, review Hardhat's documentation on [Getting Started with Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started#overview). Install the following tools: - [Node.js](https://nodejs.org/en) v18.0 or higher - [npm](https://nodejs.org/en/learn/getting-started/an-introduction-to-the-npm-package-manager) or [yarn](https://yarnpkg.com) :::tip [Hardhat for Visual Studio Code](https://hardhat.org/hardhat-vscode) is the official Hardhat extension that adds advanced support for Solidity to VSCode. If you use VSCode, give it a try! ::: If you're new to Foundry, review Foundry's documentation on [Getting Started with Foundry](https://book.getfoundry.sh). Install the following tools: - [Foundry](https://getfoundry.sh/) ## Clone the template 1. Clone the [flare-hardhat-starter](https://github.com/flare-foundation/flare-hardhat-starter) and navigate into the project directory. ```bash git clone https://github.com/flare-foundation/flare-hardhat-starter.git cd flare-hardhat-starter ``` 2. Install the project dependencies. ```bash yarn ``` or alternatively: ```bash npm install --force ``` 3. Copy the example environment file and update it with your settings. ```bash cp .env.example .env ``` 4. Open the `.env` file and set your `PRIVATE_KEY`. ```text PRIVATE_KEY=your_private_key_here ``` :::danger - Never share your private keys. - Never put your private keys in source code. - Never commit private keys to a Git repository. ::: 1. Clone the [flare-foundry-starter](https://github.com/flare-foundation/flare-foundry-starter) and navigate into the project directory. ```bash git clone https://github.com/flare-foundation/flare-foundry-starter.git cd flare-foundry-starter ``` 2. Install the project dependencies. ```bash make install ``` The `make install` target runs `forge soldeer install` and additionally fetches the npm-only dependencies (`ftso-adapters`, `pyth-sdk-solidity`, `lz-address-book`) used by the starter's adapter and OFT examples. 3. The starter ships with a `remappings.txt` ready for the current periphery release. For reference, it contains: ```text title="remappings.txt" @openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.2.0-rc.1/ flare-periphery/=dependencies/flare-periphery-0.1.37/ @flarenetwork/flare-periphery-contracts/=dependencies/flare-periphery-0.1.37/src/ forge-std/=dependencies/forge-std-1.9.5/src/ surl/=dependencies/surl-0.0.0/src/ surl/=dependencies/surl-0.0.0/ lz-address-book/=dependencies/lz-address-book/src/ @layerzerolabs/lz-evm-protocol-v2/=dependencies/lz-address-book/lib/LayerZero-v2/packages/layerzero-v2/evm/protocol/ @layerzerolabs/lz-evm-messagelib-v2/=dependencies/lz-address-book/lib/LayerZero-v2/packages/layerzero-v2/evm/messagelib/ @layerzerolabs/oft-evm/=dependencies/lz-address-book/lib/devtools/packages/oft-evm/ @layerzerolabs/oapp-evm/=dependencies/lz-address-book/lib/devtools/packages/oapp-evm/ solidity-bytes-utils/=dependencies/lz-address-book/lib/solidity-bytes-utils/ solady/=dependencies/solady-0.1.26/ ftso-adapters/=dependencies/ftso-adapters-0.0.1/ @pythnetwork/pyth-sdk-solidity/=dependencies/pyth-sdk-solidity-2.2.0/ ``` 4. Copy `.env.example` to `.env` and update it with your settings. Add `PRIVATE_KEY` to your environment variables. ```text PRIVATE_KEY=your_private_key_here ``` :::danger - Never share your private keys. - Never put your private keys in source code. - Never commit private keys to a Git repository. ::: 5. You now need to add the information from the `.env` file to your bash profile: ```bash source .env ``` You need to do this every time you open a new terminal or change the `.env` file. ## Compile, test and deploy 1. Compile the smart contracts to generate the necessary artifacts. ```bash yarn hardhat compile ``` or alternatively: ```bash yarn hardhat compile ``` This command compiles all `.sol` files in the `/contracts` folder and generates artifacts needed for testing. 2. Run the provided test suite to ensure everything is set up correctly. ```bash yarn hardhat test ``` or alternatively: ```bash yarn hardhat test ``` 3. Review and modify `hardhat.config.ts` to specify the networks you want to deploy to. The details for Flare Mainnet, Flare Testnet Coston2, Songbird Canary-Network and Songbird Testnet Coston are already included. Optionally you can add the API keys for [Flare Explorer](https://flare-explorer.flare.network) in the `.env` file. ```bash yarn hardhat run scripts/tryDeployment.ts ``` or alternatively: ```bash yarn hardhat run scripts/tryDeployment.ts --network coston2 ``` Congratulations! You have successfully integrated Flare into Hardhat. 1. Compile the smart contracts to generate the necessary artifacts. ```bash forge build ``` This command compiles all `.sol` files in the `/src` folder and generates artifacts needed for testing. 2. Run the provided test suite to ensure everything is set up correctly. ```bash forge test ``` 3. Run the deployment script using Foundry: ```bash forge script script/HelloWorld.s.sol --broadcast --private-key $PRIVATE_KEY --rpc-url ``` Replace `` with the RPC endpoint of the network you are deploying to. A list of RPC endpoints for Flare networks can be found in Flare's [Network Configuration](/network/overview#configuration). Congratulations! You have successfully integrated Flare into Foundry. --- ## Manage FlareDrops :::warning[FlareDrops have ended] FlareDrops concluded on January 30, 2026, completing the 36-month distribution schedule defined under [FIP.01](https://proposals.flare.network/FIP/FIP_1.html). WFLR, rFLR, and staked FLR no longer accrue the FlareDrop reward component. However, **protocol-level rewards remain unchanged**: you continue to earn rewards for FTSO delegation, FLR staking, and FAssets agent participation. Learn more about [FLR's operational utility era](https://flare.network/news/beyond-flaredrops-flr-enters-operational-utility-era). ::: [FlareDrops](https://flare.network/news/flaredrop-guide) are a series of 36 monthly drops totalling 24.2 billion FLR can be claimed by active Flare community members who have wrapped their Flare tokens. This guide explains how to manage FlareDrop functionality in applications.
**Understanding Personal Delegation Accounts (PDAs).** Differences between PDAs and regular accounts: - A PDA cannot have another PDA of its own. - PDA addresses cannot participate in governance directly, but their owners can transfer all their votes to another address (their main account or someone else's). - A PDA automatically converts any FLR tokens transferred to it to wrapped Flare tokens (WFLR), which are more useful for functions such as delegation. - Only the owner of the main account can transfer funds from the PDA and only to the main account. - When an executor is configured, it will claim rewards both from the main account and the PDA, and send them to the PDA.
**Understanding the Registered Claim Process.** Registration allows accounts to list themselves onchain as registered executors and post their service fees. This simplifies the process for both users and executors: users can easily find a suitable executor, and executors benefit from automatic fee transfers when user rewards are claimed. Users pay a fee to set an executor for claiming their rewards, which are then claimed automatically without user intervention. All agreements with a registered executor occur onchain. Here is how the registered claiming process works, with applications performing these actions on behalf of executors and users: 1. Executors who want to be publicly available to users register as executors by paying a registration fee, which is then burned. 2. Registered executors post their fees for claiming rewards. 3. Users with accrued rewards who want an executor to claim on their behalf can choose from the list of registered executors. 4. Users pay a setup fee to enable a registered executor to claim their rewards, and this fee is sent to the executor. 5. Executors claim rewards for one or more users, with their fees automatically deducted from the claimed rewards. 6. Executors notify users offchain if they discontinue providing this service. Throughout the process: - Users and executors can view reports on which addresses executors are claiming for and which executors are registered. - Registered executors can change their fees or unregister, while users can change the registered executor claiming on their behalf or disable automatic claiming.
## Contracts Working with the FlareDrop requires interacting with these contracts: - `DistributionToDelegators` ([address](/network/solidity-reference), [interface](/network/solidity-reference/IDistributionToDelegators)): Manages FlareDrop claims. - `ClaimSetupManager` ([address](/network/solidity-reference), [interface](/network/solidity-reference/IClaimSetupManager)): Automating reward claiming. ## Basic Claiming The [`claim`](/network/solidity-reference/IDistributionToDelegators#claim) method on `DistributionToDelegators` allows claiming the FlareDrop one account at a time. It transfers the FlareDrop rewards accrued by account `_rewardOwner` during the specified `_month` to the specified `_recipient`. `_wrap` controls whether the reward is transferred in native FLR tokens or wrapped in WFLR tokens. You can use [`getCurrentMonth()`](/network/solidity-reference/IDistributionToDelegators#getcurrentmonth) to find out the current month (starting at 0), or [`getClaimableMonths()`](/network/solidity-reference/IDistributionToDelegators#getclaimablemonths) to get the interval of months which are currently available for claiming. ```solidity function claim( address _rewardOwner, address _recipient, uint256 _month, bool _wrap ) external returns( uint256 _rewardAmount ); ``` :::note Use [`getClaimableAmount()`](/network/solidity-reference/IDistributionToDelegators#getclaimableamount) or [`getClaimableAmountOf()`](/network/solidity-reference/IDistributionToDelegators#getclaimableamountof) to find out if a given address has pending rewards on any given month. ::: The `claim()` function returns the amount of claimed rewards. Two modes of operation are supported: - **Self-Claiming**: When `msg.sender` matches `_rewardOwner` Here, the caller is claiming its own rewards, and the `_recipient` can be any address. - **Claiming on behalf of another account**: When `msg.sender` does not match `_rewardOwner` Here, the caller must be a claim executor, claiming on behalf of `_rewardOwner`. If `_msg.sender` is not in the authorized list of executors for `_rewardOwner`, the call will revert. Authorized executors must be set beforehand by `_rewardOwner` using [`setClaimExecutors()`](/network/solidity-reference/IClaimSetupManager#setclaimexecutors). The `_recipient` must either be `_rewardOwner`, its PDA, or any of the authorized recipients previously set by `_rewardOwner` using the [`setAllowedClaimRecipients()`](/network/solidity-reference/IClaimSetupManager#setallowedclaimrecipients) on `ClaimSetupManager`. The call will revert otherwise. ## Batched Claiming The [`autoClaim()`](/network/solidity-reference/IDistributionToDelegators#autoclaim) method allows claiming the FlareDrop for an arbitrary amount of accounts in a single call, with convenient default values. It claims the rewards accrued by all the accounts in the `_rewardOwners` array during the specified `_month`. If an account does not have an enabled PDA, the rewards are sent to the same account. However, if an account does have an enabled PDA, the rewards are sent to the PDA account. Any rewards accrued by the PDA account are also claimed and sent to the PDA. Rewards claimed with this method are always wrapped. ```solidity function autoClaim( address[] calldata _rewardOwners, uint256 _month ) external; ``` If the executor is a registered executor with a nonzero fee, the fee is automatically deducted from each claimed reward and sent to the executor account (unwrapped). If rewards are claimed for both an address and its PDA, the fee is deducted only once. The call reverts if: - `msg.sender` is not in the authorized list of executors for any of the `_rewardOwners`. - The total claimed rewards for any of the `_rewardOwners` is not high enough to cover the executor's fee. --- ## Network Guides Explore guides for building on Flare. Whether you are just getting started or looking for advanced topics, these guides cover everything from language-specific quickstarts to on-chain interactions and protocol features. ## Getting Started Get started building on Flare with your preferred language or framework. ## Onchain Guides Learn how to interact with Flare smart contracts and onchain features. ## Protocol Guides Learn how to interact with Flare protocol features like FlareDrops, staking, and more. --- ## Secure Random Numbers This guide shows how to obtain secure, uniform random numbers on Flare. Secure randomness is generated by the [Scaling](/ftso/scaling/overview) protocol, where approximately 100 independent data providers generate local randomness every 90 seconds (a voting round), commit onchain, then reveal. The protocol aggregates these values into a final, uniformly distributed random number. As long as **any one provider** generates an honest, hidden $n$-bit random value, the sum modulo $2^n$ remains uniform. If manipulation is detected (e.g., a reveal omission), the round's `isSecure` flag is set to false, offenders are penalized, and their values are excluded for subsequent rounds.
Understand the secure random mechanism. As described in the [FTSOv2 whitepaper](/pdf/whitepapers/20240223-FlareTimeSeriesOracleV2.pdf), the Scaling protocol consists of the following phases: 1. **Commit:** During the Commit phase, data providers prepare their submissions for each of the data feeds and encode them into a 4-byte vector. Then, each data provider publishes on chain a hash commitment obtained as: `Hash(address, voting_epoch_id, random_number, price_data)` - **Random Number**: This Commit includes a locally generated random number. - **Purpose**: The random number blinds the Commit hash of the user from a search attack and is used later (once revealed) to contribute to onchain randomness. 2. **Reveal:** During the Reveal phase, each data provider reveals all inputs to their hash commitment. As such, all locally produced random numbers become available onchain. 3. **Signing:** After the Reveal phase, data providers perform a number of local computations relevant to the Scaling protocol, which include: - Computing the weighted median prices - Calculating the rewards All these are packaged into a Merkle root, which is published onchain together with a signature of the root. 4. **Finalization:** Once enough signatures for the same Merkle root are gathered, the process is finalized. **Secure Random Numbers** For each voting round (90 seconds), let each provider's random value be $r_i \in [0, 2^n -1]$. The voting round's random number is: $$ R = \left( \sum_{i} r_i \right) \mod{2^n} $$ Because commits are fixed before reveals, if any one $r_i$ is uniformly random and not chosen adaptively after seeing others, $R$ is uniformly random over $[0, 2^n -1]$.
## Use secure random onchain :::tip You can integrate secure random numbers into your application on Flare for no cost. ::: {SecureRandomConsumer} {/* prettier-ignore */} Open in Remix In addition to the `randomNumber` itself, two other variables are retrieved: - `isSecure`: A boolean flag indicating whether the random number was generated securely: - **True**: All providers that committed also revealed, and every reveal matched its commit. - **False**: at least one omission or mismatch occurred for the round. Offending providers are penalized and excluded for a number of future rounds; the voting round's randomness is flagged as unsafe. - `timestamp`: The UNIX timestamp marking the end of the voting round during which data was collected from data providers to generate the specific number. Each voting round lasts for a fixed 90-second window. :::warning[Set EVM Version to Cancun] - **Using Remix:** Set EVM version to `cancun` under **Solidity Compiler** → **Advanced Configurations**. - **Using Hardhat or Foundry:** Set EVM version to `cancun` in [hardhat.config.ts](https://github.com/flare-foundation/flare-hardhat-starter/blob/master/hardhat.config.ts#L34) or [foundry.toml](https://github.com/flare-foundation/flare-foundry-starter/blob/master/foundry.toml). - **Using Standard Solidity JSON:** Set `"evmVersion": "cancun"` in `settings`. - **Using `solc`:** Add `--evm-version cancun` flag to your command. ::: ### Example lottery application This contract implements an example simple lottery system that utilizes a secure random number to select a winner. Participants can enter the lottery, and the winner is drawn using the secure random number generated by `RandomNumberV2`. {RandomNumberV2Lottery} {/* prettier-ignore */} Open in Remix ## Use secure random offchain To obtain a secure random number offchain, you need two key pieces of information: 1. **RPC Endpoint URL:** The RPC Endpoint URL determines which network your code will interact with. You can use a node provider service or point to your [own RPC node](/run-node#rpc-node). A comprehensive list of public and private RPC endpoints for all Flare networks is available on the [Network Configuration](/network/overview#configuration) page. 2. **Contract Address:** The address for the `RandomNumberV2` contract varies by network. You can obtain this address in two ways: - **From the Solidity Reference page:** Find the `RandomNumberV2` address for each network on the [Solidity Reference](/network/solidity-reference) page. **OR** - **Query the FlareContractRegistry Contract:** The `FlareContractRegistry` contract has the same address across all networks. You can query it to get the `RandomNumberV2` contract address. Refer to the specific language guides for examples: - [JavaScript](/network/guides/flare-for-javascript-developers#make-query) - [Python](/network/guides/flare-for-python-developers#make-query) - [Rust](/network/guides/flare-for-rust-developers#make-query) - [Go](/network/guides/flare-for-go-developers#make-query) :::tip[Polling interval] The value updates every 90s (voting round). Poll no more than once per round or subscribe to the contract's update in your client. ::: This example uses [web3.js](https://github.com/web3/web3.js) to retrieve a secure random number on Flare Testnet Coston2. ```bash npm install web3 ``` {SecureRandomWeb3Js} This example uses [ethers.js](https://github.com/ethers-io/ethers.js/) to retrieve a secure random number on Flare Testnet Coston2. ```bash npm install ethers ``` {SecureRandomEthersJs} This example uses [web3.py](https://github.com/ethereum/web3.py) to retrieve a secure random number on Flare Testnet Coston2. ```bash uv add web3 ``` ```bash pip install web3 ``` {SecureRandomWeb3Py} This example uses [alloy-rs](https://github.com/alloy-rs) to retrieve a secure random number on Flare Testnet Coston2. ```bash cargo add alloy eyre tokio ``` {SecureRandomRust} This example uses the Go API from [Geth](https://geth.ethereum.org) to retrieve a secure random number on Flare Testnet Coston2. ```bash go get github.com/ethereum/go-ethereum/ethclient ``` The project structure should look like: ```plaintext developer-hub-go/ ├── coston2/ │ └── *.go ├── flare/ │ └── *.go ├── main.go ├── go.mod └── go.sum ``` With Go, you need to manually fetch the contract's ABI and generate the Go bindings. Look up the `RandomNumberV2` address by name via the [FlareContractRegistry](/network/guides/flare-contracts-registry) on your target network, then fetch the ABI from the routescan API for that address. For Coston2 (chain id 114) the route is `https://api.routescan.io/v2/network/testnet/evm/114/etherscan/api?module=contract&action=getabi&address=&format=raw`; for Flare mainnet use `https://api.routescan.io/v2/network/mainnet/evm/14/etherscan/api?...`. Save the response as `RandomNumberV2.abi` next to `go.mod` and generate the Go bindings with [abigen](https://geth.ethereum.org/docs/tools/abigen). ```bash abigen --abi RandomNumberV2.abi --pkg coston2 --type RandomNumberV2 --out coston2/RandomNumberV2.go ``` {SecureRandomGo} ## Watch the video --- ## Using Flare Stake Tool Staking works by locking funds for a period of time to support a specific network validator. The guide details out how to stake using the [flare-stake-tool](https://github.com/flare-foundation/flare-stake-tool) CLI.
Understand staking on Flare. When validator owners stake to their own nodes they **self-bond**, whereas all other participants are said to **delegate** their stake to that validator. Note that delegating your stake to a validator is different from FTSO delegation. Participants choose how much to stake and for how long their stake will be locked. The minimum values are: | | Self-bond | Delegation | | ---------------- | --------: | ---------: | | Minimum amount | 1M FLR | 50K FLR | | Minimum duration | 60 days | 14 days | At the end of every reward epoch (i.e. 3.5 days on Flare Mainnet), participants are rewarded according to how well their chosen validator performed in that period. **Limits** The amount that you can stake and the rewards you can gain by staking are restricted by these limits: - **Delegation factor**: Limits the total amount that can be staked to a validator to its self-bond, which is the amount validators stake to their own nodes, times the delegation factor, which is 15. For example, if a validator has a self-bond stake of 1M FLR, the total sum of all stakes, including delegations, cannot exceed 15M FLR. This limit allows for 14M FLR of delegations. - **Staking cap**: Limits the reward performance of individual validators to **5% of the total staked amount**. If you stake your funds on a validator with more than 5% of the total staked amount, you receive less FLR in reward. To maximize your reward, delegate your staking funds to a validator with less than 5% of the total staked amount of FLR. - **Maximum number of validators**: You can stake to any number of validators, but rewards, FlareDrops, and governance vote power only apply for up to 3 different validators. Given that the Flare network uses two independent underlying chains, there is one extra step that must be considered. Funds must be transferred from the C-chain, where smart contracts run, to the P-chain, where staking happens. After the staking period expires and funds are unlocked, they can be transferred back to the C-chain.
## Prerequisites Install the following tools: - [Node.js 18 LTS or newer](https://nodejs.org/) (which includes [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm/)) ## Setup 1. Install the `flare-stake-tool` CLI: ```bash npm install @flarenetwork/flare-stake-tool -g ``` 2. Verify installation: ```bash flare-stake-tool ``` The tool's banner is displayed: ```text _____ _ ____ _ _ ____ _ ___ | ___| | __ _ _ __ ___ / ___|| |_ __ _| | _____ / ___| | |_ _| | |_ | |/ _` | '__/ _ \ \___ \| __/ _` | |/ / _ \ | | | | | | | _| | | (_| | | | __/ ___) | || (_| | < __/ | |___| |___ | | |_| |_|\__,_|_| \___| |____/ \__\__,_|_|\_\___| \____|_____|___| Version: 4.x ``` 3. You can specify the account from which staking will take place in different ways - [using Ledger](#using-ledger-recommended) or [using a private key](#using-private-key). The only recommended way is to use a Ledger device. ### Using Ledger (recommended) 1. Connect your Ledger device to your computer and unlock it. Launch the [Ledger Wallet](https://shop.ledger.com/pages/ledger-wallet) (previously called Ledger Live) application and navigate to the **My Ledger** tab and ensure your device firmware is up-to-date. 2. In the **App Catalog** tab, search for **Avalanche**, Click **Install**. :::info[App Size] The Avalanche app requires 138 KB of space on a **Ledger Nano S**. You may need to remove other apps if space is insufficient. ::: 3. Exit Ledger Wallet and ensure the device is not connected to any other applications like MetaMask. Open the Avalanche app on your Ledger device. The screen should display **Avalanche Ready**. 4. Open a terminal and run the following command to start the staking tool in interactive mode: ```bash flare-stake-tool interactive ``` 5. After the welcome banner, the tool prompts you to connect your wallet: ```text ? How do you want to connect your wallet? (Use arrow keys) > Ledger Public Key Private Key (not recommended) ``` Select **Ledger** using the arrow keys and press **Enter**. 6. The next prompt asks you to choose the network: ```text ? Which network do you want to connect to? (Use arrow keys) > Flare (Mainnet) Coston2 (Testnet) LocalHost (for development only) ``` Select **Flare (Mainnet)** and press **Enter**. A "Fetching Addresses..." message appears briefly. Select **Ledger Live** on the address derivation screen. 7. The tool will then display a list of addresses linked to your Ledger device, select the desired address and press **Enter**. Ensure this address has a sufficient `FLR` balance for transaction fees and staking. Funds can be transferred later if needed. The main menu will now appear: ```text ? What do you want to do? (Use arrow keys) View chain addresses > Check on-chain balance Get network info Get validator info Move assets from C-chain to P-chain Move assets from P-chain to C-chain Add a validator node ``` As an example, select **Check on-chain balance** and press **Enter**. The tool displays the C-chain and P-chain balances before exiting. :::tip[Account Persistence] After completing the setup, a `ctx.json` file is created in the current folder. This file stores the selected account details, allowing you to skip the setup process on subsequent uses from the same folder. ::: ### Using private key :::danger[Using a private key file] Using a private key stored in a plain text file is **insecure** and should be avoided whenever possible. It is highly recommended to [use Ledger](#using-ledger-recommended) instead. ::: 1. Create a text file (e.g. `/home/wallet/pvtKeyFile`) in a **secure folder** accessible only to you. 2. Open the file and insert one of the following lines based on your private key format: ```plaintext PRIVATE_KEY_CB58="" // Use this if your key is in CB58 format, keep the quotes. PRIVATE_KEY_HEX="" // Use this if your key contains 64 hexadecimal characters, keep the quotes. ``` 3. Open a terminal and run the following command to start the staking tool in interactive mode: ```bash flare-stake-tool interactive ``` 4. After the welcome banner, the tool prompts you to connect your wallet: ```plaintext ? How do you want to connect your wallet? (Use arrow keys) Ledger Public Key > Private Key (not recommended) ``` Select **Private Key** using the arrow keys and press **Enter**. 5. You'll see a security warning, enter the full file path where your private key file is stored and press **Enter**. ```text Warning: You are connecting using your private key which is not recommended ? Enter Path to Private Key file (E.g. /home/wallet/pvtKeyFile): ``` 6. The next prompt asks you to choose the network: ```plaintext ? Which network do you want to connect to? (Use arrow keys) > Flare (Mainnet) Coston2 (Testnet) LocalHost (for development only) ``` Select **Flare (Mainnet)** and press **Enter**. 7. The main menu will now appear: ```text ? What do you want to do? (Use arrow keys) View chain addresses > Check on-chain balance Get network info Get validator info Move assets from C-chain to P-chain Move assets from P-chain to C-chain Add a validator node ``` As an example, select **Check on-chain balance** and press **Enter**. The tool displays the C-chain and P-chain balances before exiting. ### Check validator info 1. Note down the `NodeID` of the validator you want to stake to: - If you created the validator, retrieve its `NodeID`, `publicKey` and `proofOfPossession` by running: ```bash curl \ --location 'http://localhost:9650/ext/info' \ --header 'Content-Type: application/json' \ --data '{ "jsonrpc":"2.0", "id":1, "method":"info.getNodeID" }' ``` - If you want to stake to somebody else's validator, get a JSON list of all validators by running: ```bash flare-stake-tool info validators ``` 2. Note down the desired staking **start time** and **end time**: When staking to an existing validator, both these times must be inside the period when the validator is active. If you specify a period when the validator is inactive, your transaction on the P-chain reverts. ## Move funds to P-chain 1. Check your C-chain and P-chain balances by selecting the **Check on-chain balance** option when executing: ```bash flare-stake-tool interactive ``` Your currently available funds on the C-chain and P-chain are shown in the last lines. Funds currently staked are locked and are not reflected in the P-chain balance. They will become automatically available when the staking period expires. ```plaintext ? How do you want to connect your wallet? Ledger You already have an existing Ctx file with the following parameters - Public Key: ●●●●●●●●●●●●●●●● Network: flare Eth Address: 0x●●●●●●●● ? Do you wish to continue with this? yes ? What do you want to do? Check on-chain balance Using network: flare Balances on the network "flare" // highlight-next-line C-chain 0x●●●●●●●●: 100000.0 FLR // highlight-next-line P-chain P-flare●●●●●●●●: 50000.0 FLR ``` If you already have funds on the P-chain, skip the next step. 2. Select the **Move assets from C-chain to P-chain** option when executing: ```bash flare-stake-tool interactive ``` You are asked the amount of FLR you want to transfer: ```plaintext ? What do you want to do? Move assets from C-chain to P-chain ? Enter amount (in FLR): 50000 ? Enter fees (in FLR): (1) ``` :::warning[Transaction Fees] When transferring from the C-chain to the P-chain, transaction fees are wholly paid from the C-chain. Make sure you leave enough funds on the C-chain after the transfer, or it will fail. ::: 3. Transfers between chains are made of two operations: an **export** from the C-chain followed by an **import** to the P-chain. Therefore, you are asked to confirm two transactions. ```plaintext // highlight-next-line Please approve export transaction Using network: flare Fetching account from ledger... Creating export transaction... Transaction finalized! // highlight-next-line Please approve import transaction Using network: flare Fetching account from ledger... Creating import transaction... ``` If you encounter any issues during this process, refer to the [Troubleshooting](#troubleshooting) section. ## Stake 1. After you have funds on the P-chain, execute the following command and select the appropriate option: ```bash flare-stake-tool interactive ``` - If you are going to delegate to your own node (self-bonding), select **Add a validator node**. - If you are going to stake to another node (delegation), select **Delegate to a validator node** (You may need to scroll down to see this option). 2. You then need to provide the following information: - **amount**: Amount must be provided in FLR units. - **NodeID** and **end time**: Use the values noted down from the [setup](#setup). If you are adding a validator node, you also need to provide the proof of possession **BLS public key** and **BLS signature**. Retrieve these details using the following command: ```bash curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, "method" :"info.getNodeID" }' -H 'content-type:application/json;' 127.0.0.1:9650/ext/info | jq ``` The output will be similar to the following: ```json { "jsonrpc": "2.0", "result": { "nodeID": "NodeID-CGeHNYkQJX4qb21x976PEcNt3FqsUZfXL", "nodePOP": { popBLSPublicKey ---> "publicKey": "0xb6a40b905128c010e4d7b0cbd6f7b7912a1da9a6022a8c720887b5183a4f2f109c678027316ae4757f7f987f1fa5addb", popBLSSignature ---> "proofOfPossession": "0xb9394ef2992a77a3b70440c2fdb3b2a53f23fb8579460322b69a290bf3de7fafc2665b155b5300c08a1e7bcb45bb7eaf0ca7f596650b0423f61f329744e37f6908c1d7e4191378d5f0ff7fd7f8abd02d37a2be9dd95635a0b1f744b043cb5d9e" } }, "id": 1 } ``` 3. If you selected **Add a validator node**, you have to set the **delegation fee**. This is the percentage of all rewards that the node owner keeps. The rest is split proportionally between the self-bond and all delegators that contributed stake. 10 means 10%, so the maximum value is 100. ```plaintext ? What do you want to do? Add a validator node ? Enter amount (in FLR): 50000 ? Enter Node NodeId (E.g. NodeID-FQKTLuZHEsjCxPeFTFgsojsucmdyNDsz1): NodeID-●●●●●●●● ? Enter end time(E.g. 1693185095): ●●●●●●●● ? Enter delegation fee(E.g. 10): 10 ? Please enter the popBLSPublicKey: 0x●●●●●●●● ? Please enter the popBLSSignature: 0x●●●●●●●● ``` 4. You are then asked to confirm the staking transaction on your hardware wallet. ```plaintext Using network: flare Fetching account from ledger... Creating export transaction... // highlight-next-line Please review and sign the transaction on your ledger device... Sending transaction to the node... Transaction with id ●●●●●●●● sent to the node Finished execution ``` Your stake is now locked and will start accruing rewards after the configured start time arrives. When the end time arrives, the funds will be automatically unlocked. If you encounter any issues, refer to [Troubleshooting](#troubleshooting). ## Check Stake 1. You can now double-check that the operation has been properly registered by looking at the current list of validators: ```bash flare-stake-tool info validators > validators.txt ``` This creates a file called `validators.txt`. Open it and search for the line containing the **P-chain address** of your account. If you don't remember your address run: ```bash flare-stake-tool info addresses ``` 2. If your account has stake on any node, you will find a section similar to: ```json { "txID": "28Yf5yQ3xt9yaMvfZ1RP5jkCkT4y2pfD86UheZUHFVng2tFcWd", "startTime": "1688569201", "endTime": "1696345201", "stakeAmount": "16750000000000000", "nodeID": "NodeID-C6i8mruq11VdxGQ7tiUBgrRqoLBot86df", "rewardOwner": { "locktime": "0", "threshold": "1", "addresses": [ // highlight-next-line "P-flare19c8zfml39x6efnw5j90nl85dmwdqhluwhrxz9g" ] } } ``` Check that the `stakeAmount` (in wei), `nodeID`, `startTime`, and `endTime` match the values you configured. If you have multiple active stakes, your address will show up multiple times. ## Move funds back to C-chain 1. Finally, you also have the option to move your P-chain funds back to the C-chain where they can participate in the wider ecosystem. You can only transfer P-chain funds that are not currently locked in any stake. Select the **Move assets from P-chain to C-chain** option when executing: ```bash flare-stake-tool interactive ``` 2. You are asked the amount of FLR you want to transfer: ```plaintext ? What do you want to do? Move assets from P-chain to C-chain ? Enter amount (in FLR): 50000 ``` :::warning[Transaction Fees] When transferring from the P to the C-chain, transaction fees are paid from BOTH chains. Make sure you leave enough funds on both chains after the transfer, or it will fail. ::: 3. Again, the transfer between the two chains require you to confirm two transactions. ```plaintext // highlight-next-line Please approve export transaction Using network: flare Fetching account from ledger... Creating export transaction... ? Enter fees (in FLR): (1) // highlight-next-line Please approve import transaction Using network: flare Fetching account from ledger... Creating import transaction... Finished execution ``` If you encounter any issues, refer to [Troubleshooting](#troubleshooting). ## Claiming staking rewards At the end of every reward epoch, participants are rewarded according to how well their chosen validator performed in that period, but these rewards are not claimable yet. Every 4 reward epochs, rewards are accumulated in a dedicated smart contract and can then be claimed from the Flare Stake CLI tool: 1. Select the **Claim staking rewards** option when executing: ```bash flare-stake-tool interactive ``` You are shown the amount of pending rewards (in wei) and are asked how much you want to claim (in FLR): ```plaintext ? What do you want to do? Claim staking rewards State of rewards for 0x●●●●●●●●: Total rewards: 2000.0 FLR Claimed rewards: 1010.0 FLR Unclaimed rewards: 990.0 FLR ``` 2. Claim all or a portion of the rewards and optionally choose to wrap them or not: ```plaintext ? Do you want to claim all unclaimed rewards? (total unclaimed: 990.0 FLR) Yes ? Should claimed rewards be wrapped? Yes ``` ```plaintext ? Do you want to claim all unclaimed rewards? (total unclaimed: 990.0 FLR) No ? Please enter the amount to claim: 100 ? Should claimed rewards be wrapped? Yes ``` 3. Decide the recipient address for the rewards - defaults to the address you used to connect to the Flare Stake Tool. ```plaintext ? Please enter the recipient address (default address is reward owner): 0x●●●●●●●● ``` You are then asked to confirm the staking transaction on your hardware wallet. ```plaintext Using network: flare Creating claim transaction... Signing transaction... // highlight-next-line Transaction with hash 0x●●●●●●●● built and sent to the network ``` ## Transfer P-chain funds to another P-chain address 1. You can transfer P-chain funds to another P-chain address by selecting the **Transfer funds to another P-chain address** option: ```bash flare-stake-tool interactive ``` ```plaintext ? What do you want to do? ❯ Transfer funds to another P-chain address Claim staking rewards Quit ``` 2. Select the amount to transfer and the destination P-chain address: ```plaintext ? Enter amount (in FLR): 100 ? Please enter the destination P-chain address: P-flare●●●●●●●● Using network: flare Fetching account from ledger... Creating transfer transaction... ``` ## Troubleshooting
T1. Cannot connect to Ledger device, no Device, cannot retrieve addresses, or similar. Make sure: - The device is connected, the Avalanche app is opened, and it shows the "Avalanche Ready" message. - No other application like Ledger Wallet or MetaMask is connected to the device. - The device is not in stand-by mode. - You are not running on Windows from a Linux terminal (WSL). Use a native Windows console instead.
T2. Insufficient funds. Make sure enough funds will remain after a transaction to pay for the transaction fees. If too much time has elapsed between the transaction's creation and its confirmation on the Ledger, the calculated fee might be incorrect. Try the operation again. The network might be congested and the calculated fees might not be high enough. Try the operation again after a while.
T3. Import transaction failed and the funds have vanished. Transfer operations require [an export and an import transaction](#move-funds-to-p-chain). If the export succeeds, but then the import fails, it looks like the funds have disappeared from both chains, but they are still retrievable. Import the funds using the interactive tool: - If you are moving funds from the C-chain to the P-chain: ```bash flare-stake-tool interactive ``` ```plaintext ? What do you want to do? ❯ Import Funds (in case export fails for either P chain or C chain) ``` ```plaintext ? Please select the destination chain to which you want to import your funds? (Use arrow keys) ❯ P ``` - If you are moving funds from the P-chain to the C-chain: ```bash flare-stake-tool interactive ``` ```plaintext ? What do you want to do? ❯ Import Funds (in case export fails for either P chain or C chain) ``` ```plaintext ? Please select the destination chain to which you want to import your funds? (Use arrow keys) ❯ C ```
T4. Unsupported digital routine. If you get the following error message: ```text E: Error: error:0308010C:digital envelope routines::unsupported ``` Make sure you are using the correct Node.js version, as advised in the [Prerequisites section](#prerequisites). You can find out the version of Node.js you are running with the following command: ```bash node --version ```
## FAQs
F1. How do I register my address manually? You can register your address manually using the block explorer and the `AddressBinder` smart contract. 1. Retrieve the public key that generated the accounts you want to use. From a terminal, run: ```bash flare-stake-tool info addresses ``` Copy the hexadecimal string starting with `0x` in the last line. ```plaintext Using network: flare Addresses on the network "flare" P-chain address: P-flare●●●●●●●● C-chain address hex: 0x●●●●●●●● // highlight-next-line secp256k1 public key: 0x●●●●●●●●●●●●●●●● ``` 2. Retrieve the `AddressBinder` contract address from the [`FlareContractRegistry`](/network/solidity-reference). 3. Enter the address of the `AddressBinder` contract in the block explorer, and go to the **Write Contract** tab. 4. Click on **Connect Wallet**. You do not need to use the same account as the one you are binding. 5. Locate the `registerPublicKey` method and paste the public key from step 1 into the `_publicKey` field. 6. Click on **Write** and confirm the transaction from your wallet. If the transaction is successful, your account's P-chain and C-chain addresses are now bound.
F2. How do I claim rewards manually? You can claim rewards manually using the block explorer and the `ValidatorRewardManager` smart contract. 1. Retrieve the `ValidatorRewardManager` contract address from the [`FlareContractRegistry`](/network/solidity-reference). 2. Enter the address of the `ValidatorRewardManager` contract in the block explorer, and go to the **Write Contract** tab. 3. Click on **Connect Wallet**. You need to connect the account for which you are claiming. 4. Locate the `claim` method and enter the following information: - `_rewardOwner`: C-chain address that accrued the rewards. - `_recipient`: Address where the rewards must be sent. - `_rewardAmount`: Amount to claim. Find the pending amount using the `getStateOfRewards` method in the **Read Contract** tab. - `_wrap`: Whether the rewards should be also wrapped, as a convenience. 5. Click on **Write** and confirm the transaction from your wallet. If the transaction is successful, the reward is transferred to the specified recipient.
--- ## Wrapped Native Tokens ## Introduction This guide will show you how to deposit and withdraw wrapped native tokens on Flare. Wrapped tokens are required to delegate your vote power to FTSO data providers and to vote on decisions that affect how Flare networks operate. Wrapped tokens are stored on the blockchain in an [`ERC-20`](https://ethereum.org/developers/docs/standards/tokens/erc-20/) token standard in a smart contract called `WNat`. The [`IWNat`](/network/solidity-reference/IWNat) contract is the interface for the Wrapped Native Token contract. In this guide, we will use the `deposit` and `withdraw` functions to wrap and unwrap native tokens: ## Deposit The `deposit` function wraps the amount of native tokens you specify in the field. You can deposit native tokens on the Flare network using the `deposit` function in the following code: {Deposit} The most crucial function in the code above is the [`deposit`](/network/solidity-reference/IWNat#deposit) function from the `IWNat` contract. With this function, you can deposit native tokens on Flare network and mint wrapped native tokens (WNAT) in return. ## Withdraw Once you have wrapped native tokens, you can withdraw them using the [`withdraw`](/network/solidity-reference/IWNat#withdraw) function. It unwraps the amount of native tokens you specify in the input field. You can withdraw native tokens on the Flare network using the `withdraw` function using the following code: {Withdraw} :::tip[What's next] You can deposit and withdraw wrapped native tokens to a specific address. Learn more in the [IWNat](/network/solidity-reference/IWNat) documentation. ::: --- ## IDistributionToDelegators :::warning[FlareDrops have ended] FlareDrops concluded on January 30, 2026, completing the 36-month distribution schedule defined under [FIP.01](https://proposals.flare.network/FIP/FIP_1.html). WFLR, rFLR, and staked FLR no longer accrue the FlareDrop reward component. However, **protocol-level rewards remain unchanged**: you continue to earn rewards for FTSO delegation, FLR staking, and FAssets agent participation. Learn more about [FLR's operational utility era](https://flare.network/news/beyond-flaredrops-flr-enters-operational-utility-era). ::: Interface for managing FlareDrop claims. Sourced from `IDistributionToDelegators.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IDistributionToDelegators.sol). Manages the ongoing distribution of tokens from the Delegation Incentive Pool (the "FlareDrop"). The approval of [FIP.01](https://proposals.flare.network/FIP/FIP_1) created this pool, that releases its tokens every 30 days, over a period of 36 months, to all accounts holding Wrapped FLR. ## Functions ### claim Allows the sender to claim or wrap rewards for reward owner. The caller does not have to be the owner, but must be approved by the owner to claim on his behalf, this approval is done by calling `setClaimExecutors`. It is actually safe for this to be called by anybody (nothing can be stolen), but by limiting who can call, we allow the owner to control the timing of the calls. Reward owner can claim to any `_recipient`, while the executor can only claim to the reward owner, reward owners's personal delegation account or one of the addresses set by `setAllowedClaimRecipients`. ```solidity function claim( address _rewardOwner, address _recipient, uint256 _month, bool _wrap ) external returns ( uint256 _rewardAmount); ``` #### Parameters - `_rewardOwner`: address of the reward owner - `_recipient`: address to transfer funds to - `_month`: last month to claim for - `_wrap`: should reward be wrapped immediately #### Returns - `_rewardAmount`: amount of total claimed rewards ### autoClaim Allows batch claiming for the list of '\_rewardOwners' up to given '\_month'. If reward owner has enabled delegation account, rewards are also claimed for that delegation account and total claimed amount is sent to that delegation account, otherwise claimed amount is sent to owner's account. Claimed amount is automatically wrapped. Method can be used by reward owner or executor. If executor is registered with fee > 0, then fee is paid to executor for each claimed address from the list. ```solidity function autoClaim( address[] _rewardOwners, uint256 _month ) external; ``` #### Parameters - `_rewardOwners`: list of reward owners to claim for - `_month`: last month to claim for ### optOutOfAirdrop Method to opt-out of receiving airdrop rewards ```solidity function optOutOfAirdrop( ) external; ``` ### nextClaimableMonth Returns the next claimable month for '\_rewardOwner'. ```solidity function nextClaimableMonth( address _rewardOwner ) external view returns ( uint256); ``` #### Parameters - `_rewardOwner`: address of the reward owner ### getClaimableAmount get claimable amount of wei for requesting account for specified month ```solidity function getClaimableAmount( uint256 _month ) external view returns ( uint256 _amountWei); ``` #### Parameters - `_month`: month of interest #### Returns - `_amountWei`: amount of wei available for this account and provided month ### getClaimableAmountOf get claimable amount of wei for account for specified month ```solidity function getClaimableAmountOf( address _account, uint256 _month ) external view returns ( uint256 _amountWei); ``` #### Parameters - `_account`: the address of an account we want to get the claimable amount of wei - `_month`: month of interest #### Returns - `_amountWei`: amount of wei available for provided account and month ### getCurrentMonth Returns the current month ```solidity function getCurrentMonth( ) external view returns ( uint256 _currentMonth); ``` #### Returns - `_currentMonth`: Current month, 0 before entitlementStartTs ### getMonthToExpireNext Returns the month that will expire next ```solidity function getMonthToExpireNext( ) external view returns ( uint256 _monthToExpireNext); ``` #### Returns - `_monthToExpireNext`: Month that will expire next, 36 when last month expired ### getClaimableMonths Returns claimable months - reverts if none ```solidity function getClaimableMonths( ) external view returns ( uint256 _startMonth, uint256 _endMonth); ``` #### Returns - `_startMonth`: first claimable month - `_endMonth`: last claimable month ## Events ### UseGoodRandomSet ```solidity event UseGoodRandomSet( bool useGoodRandom, uint256 maxWaitForGoodRandomSeconds ) ``` ### EntitlementStart ```solidity event EntitlementStart( uint256 entitlementStartTs ) ``` ### AccountClaimed ```solidity event AccountClaimed( address whoClaimed, address sentTo, uint256 month, uint256 amountWei ) ``` ### AccountOptOut ```solidity event AccountOptOut( address theAccount, bool confirmed ) ``` --- ## IEntityManager Manages voter entities, including addresses and node IDs. Sourced from `IEntityManager.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IEntityManager.sol). ## Functions ### confirmDelegationAddressRegistration Confirms a delegation address registration (called by the delegation address). ```solidity function confirmDelegationAddressRegistration( address _voter ) external; ``` #### Parameters - `_voter`: Voter address. ### confirmSigningPolicyAddressRegistration Confirms a signing policy address registration (called by the signing policy address). ```solidity function confirmSigningPolicyAddressRegistration( address _voter ) external; ``` #### Parameters - `_voter`: Voter address. ### confirmSubmitAddressRegistration Confirms a submit address registration (called by the submit address). ```solidity function confirmSubmitAddressRegistration( address _voter ) external; ``` #### Parameters - `_voter`: Voter address. ### confirmSubmitSignaturesAddressRegistration Confirms a submit signatures address registration (called by the submit signatures address). ```solidity function confirmSubmitSignaturesAddressRegistration( address _voter ) external; ``` #### Parameters - `_voter`: Voter address. ### getDelegationAddressOf Gets the delegation address of a voter at the current block number. ```solidity function getDelegationAddressOf( address _voter ) external view returns ( address ); ``` #### Parameters - `_voter`: Voter address. #### Returns - ``: Public key. ### getDelegationAddressOfAt Gets the delegation address of a voter at a specific block number. ```solidity function getDelegationAddressOfAt( address _voter, uint256 _blockNumber ) external view returns ( address ); ``` #### Parameters - `_voter`: Voter address. - `_blockNumber`: Block number. #### Returns - ``: Public key. ### getNodeIdsOf Gets the node ids of a voter at the current block number. ```solidity function getNodeIdsOf( address _voter ) external view returns ( bytes20[] ); ``` #### Parameters - `_voter`: Voter address. #### Returns - ``: Node ids. ### getNodeIdsOfAt Gets the node ids of a voter at a specific block number. ```solidity function getNodeIdsOfAt( address _voter, uint256 _blockNumber ) external view returns ( bytes20[] ); ``` #### Parameters - `_voter`: Voter address. - `_blockNumber`: Block number. #### Returns - ``: Node ids. ### getPublicKeyOf Gets the public key of a voter at the current block number. ```solidity function getPublicKeyOf( address _voter ) external view returns ( bytes32, bytes32 ); ``` #### Parameters - `_voter`: Voter address. #### Returns - ``: Public key. - ``: ### getPublicKeyOfAt Gets the public key of a voter at a specific block number. ```solidity function getPublicKeyOfAt( address _voter, uint256 _blockNumber ) external view returns ( bytes32, bytes32 ); ``` #### Parameters - `_voter`: Voter address. - `_blockNumber`: Block number. #### Returns - ``: Public key. - ``: ### getVoterAddresses Gets voter's addresses at the current block number. ```solidity function getVoterAddresses( address _voter ) external view returns ( struct IEntityManager.VoterAddresses _addresses ); ``` #### Parameters - `_voter`: Voter address. #### Returns - `_addresses`: Voter addresses. ### getVoterAddressesAt Gets voter's addresses at a specific block number. ```solidity function getVoterAddressesAt( address _voter, uint256 _blockNumber ) external view returns ( struct IEntityManager.VoterAddresses _addresses ); ``` #### Parameters - `_voter`: Voter address. - `_blockNumber`: Block number. #### Returns - `_addresses`: Voter addresses. ### getVoterForDelegationAddress Gets voter's address for a delegation address at a specific block number. ```solidity function getVoterForDelegationAddress( address _delegationAddress, uint256 _blockNumber ) external view returns ( address _voter ); ``` #### Parameters - `_delegationAddress`: Delegation address. - `_blockNumber`: Block number. #### Returns - `_voter`: Voter address. ### getVoterForNodeId Gets voter's address for a node id at a specific block number. ```solidity function getVoterForNodeId( bytes20 _nodeId, uint256 _blockNumber ) external view returns ( address _voter ); ``` #### Parameters - `_nodeId`: Node id. - `_blockNumber`: Block number. #### Returns - `_voter`: Voter address. ### getVoterForPublicKey Gets voter's address for a public key at a specific block number. ```solidity function getVoterForPublicKey( bytes32 _part1, bytes32 _part2, uint256 _blockNumber ) external view returns ( address _voter ); ``` #### Parameters - `_part1`: First part of the public key. - `_part2`: Second part of the public key. - `_blockNumber`: Block number. #### Returns - `_voter`: Voter address. ### getVoterForSigningPolicyAddress Gets voter's address for a signing policy address at a specific block number. ```solidity function getVoterForSigningPolicyAddress( address _signingPolicyAddress, uint256 _blockNumber ) external view returns ( address _voter ); ``` #### Parameters - `_signingPolicyAddress`: Signing policy address. - `_blockNumber`: Block number. #### Returns - `_voter`: Voter address. ### getVoterForSubmitAddress Gets voter's address for a submit address at a specific block number. ```solidity function getVoterForSubmitAddress( address _submitAddress, uint256 _blockNumber ) external view returns ( address _voter ); ``` #### Parameters - `_submitAddress`: Submit address. - `_blockNumber`: Block number. #### Returns - `_voter`: Voter address. ### getVoterForSubmitSignaturesAddress Gets voter's address for a submit signatures address at a specific block number. ```solidity function getVoterForSubmitSignaturesAddress( address _submitSignaturesAddress, uint256 _blockNumber ) external view returns ( address _voter ); ``` #### Parameters - `_submitSignaturesAddress`: Submit signatures address. - `_blockNumber`: Block number. #### Returns - `_voter`: Voter address. ### proposeDelegationAddress Proposes a delegation address (called by the voter). ```solidity function proposeDelegationAddress( address _delegationAddress ) external; ``` #### Parameters - `_delegationAddress`: Delegation address. ### proposeSigningPolicyAddress Proposes a signing policy address (called by the voter). ```solidity function proposeSigningPolicyAddress( address _signingPolicyAddress ) external; ``` #### Parameters - `_signingPolicyAddress`: Signing policy address. ### proposeSubmitAddress Proposes a submit address (called by the voter). ```solidity function proposeSubmitAddress( address _submitAddress ) external; ``` #### Parameters - `_submitAddress`: Submit address. ### proposeSubmitSignaturesAddress Proposes a submit signatures address (called by the voter). ```solidity function proposeSubmitSignaturesAddress( address _submitSignaturesAddress ) external; ``` #### Parameters - `_submitSignaturesAddress`: Submit signatures address. ### registerNodeId Registers a node id. ```solidity function registerNodeId( bytes20 _nodeId, bytes _certificateRaw, bytes _signature ) external; ``` #### Parameters - `_nodeId`: Node id. - `_certificateRaw`: Certificate in raw format. - `_signature`: Signature. ### registerPublicKey Registers a public key. ```solidity function registerPublicKey( bytes32 _part1, bytes32 _part2, bytes _verificationData ) external; ``` #### Parameters - `_part1`: First part of the public key. - `_part2`: Second part of the public key. - `_verificationData`: Additional data used to verify the public key. ### unregisterNodeId Unregisters a node id. ```solidity function unregisterNodeId( bytes20 _nodeId ) external; ``` #### Parameters - `_nodeId`: Node id. ### unregisterPublicKey Unregisters a public key. ```solidity function unregisterPublicKey( ) external; ``` ## Events ### DelegationAddressProposed Event emitted when a delegation address is proposed. ```solidity event DelegationAddressProposed( address voter, address delegationAddress ) ``` ### DelegationAddressRegistrationConfirmed Event emitted when a delegation address registration is confirmed. ```solidity event DelegationAddressRegistrationConfirmed( address voter, address delegationAddress ) ``` ### MaxNodeIdsPerEntitySet Event emitted when the maximum number of node ids per entity is set. ```solidity event MaxNodeIdsPerEntitySet( uint256 maxNodeIdsPerEntity ) ``` ### NodeIdRegistered Event emitted when a node id is registered. ```solidity event NodeIdRegistered( address voter, bytes20 nodeId ) ``` ### NodeIdUnregistered Event emitted when a node id is unregistered. ```solidity event NodeIdUnregistered( address voter, bytes20 nodeId ) ``` ### PublicKeyRegistered Event emitted when a public key is registered. ```solidity event PublicKeyRegistered( address voter, bytes32 part1, bytes32 part2 ) ``` ### PublicKeyUnregistered Event emitted when a public key is unregistered. ```solidity event PublicKeyUnregistered( address voter, bytes32 part1, bytes32 part2 ) ``` ### SigningPolicyAddressProposed Event emitted when a signing policy address is proposed. ```solidity event SigningPolicyAddressProposed( address voter, address signingPolicyAddress ) ``` ### SigningPolicyAddressRegistrationConfirmed Event emitted when a signing policy address registration is confirmed. ```solidity event SigningPolicyAddressRegistrationConfirmed( address voter, address signingPolicyAddress ) ``` ### SubmitAddressProposed Event emitted when a submit address is proposed. ```solidity event SubmitAddressProposed( address voter, address submitAddress ) ``` ### SubmitAddressRegistrationConfirmed Event emitted when a submit address registration is confirmed. ```solidity event SubmitAddressRegistrationConfirmed( address voter, address submitAddress ) ``` ### SubmitSignaturesAddressProposed Event emitted when a submit signatures address is proposed. ```solidity event SubmitSignaturesAddressProposed( address voter, address submitSignaturesAddress ) ``` ### SubmitSignaturesAddressRegistrationConfirmed Event emitted when a submit signatures address registration is confirmed. ```solidity event SubmitSignaturesAddressRegistrationConfirmed( address voter, address submitSignaturesAddress ) ``` ## Structures ### VoterAddresses Voter addresses. ```solidity struct VoterAddresses { address submitAddress; address submitSignaturesAddress; address signingPolicyAddress; } ``` --- ## IFlareSystemsCalculator Performs calculations for weights and burn factors used by other contracts. Sourced from `IFlareSystemsCalculator.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFlareSystemsCalculator.sol). ## Functions ### signingPolicySignNoRewardsDurationBlocks Number of blocks (in addition to non-punishable blocks) after which all rewards are burned. ```solidity function signingPolicySignNoRewardsDurationBlocks( ) external view returns ( uint64 ); ``` ### signingPolicySignNonPunishableDurationBlocks Number of non-punishable blocks to sign new signing policy. ```solidity function signingPolicySignNonPunishableDurationBlocks( ) external view returns ( uint64 ); ``` ### signingPolicySignNonPunishableDurationSeconds Non-punishable time to sign new signing policy. ```solidity function signingPolicySignNonPunishableDurationSeconds( ) external view returns ( uint64 ); ``` ### wNatCapPPM WNat cap used in signing policy weight. ```solidity function wNatCapPPM( ) external view returns ( uint24 ); ``` ## Events ### VoterRegistrationInfo Event emitted when the registration weight of a voter is calculated. ```solidity event VoterRegistrationInfo( address voter, uint24 rewardEpochId, address delegationAddress, uint16 delegationFeeBIPS, uint256 wNatWeight, uint256 wNatCappedWeight, bytes20[] nodeIds, uint256[] nodeWeights ) ``` --- ## IFlareSystemsManager Manages system protocols like the Signing Policy Definition, Uptime Voting, and Reward Voting. Sourced from `IFlareSystemsManager.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFlareSystemsManager.sol). ## Functions ### firstRewardEpochStartTs Timestamp when the first reward epoch started, in seconds since UNIX epoch. ```solidity function firstRewardEpochStartTs( ) external view returns ( uint64 ); ``` ### firstVotingRoundStartTs Timestamp when the first voting epoch started, in seconds since UNIX epoch. ```solidity function firstVotingRoundStartTs( ) external view returns ( uint64 ); ``` ### getCurrentRewardEpoch Returns the current reward epoch id (backwards compatibility). ```solidity function getCurrentRewardEpoch( ) external view returns ( uint256 ); ``` ### getCurrentRewardEpochId Returns the current reward epoch id. ```solidity function getCurrentRewardEpochId( ) external view returns ( uint24 ); ``` ### getCurrentVotingEpochId Returns the current voting epoch id. ```solidity function getCurrentVotingEpochId( ) external view returns ( uint32 ); ``` ### getSeed Returns the seed for given reward epoch id. ```solidity function getSeed( uint256 _rewardEpochId ) external view returns ( uint256 ); ``` ### getStartVotingRoundId Returns the start voting round id for given reward epoch id. ```solidity function getStartVotingRoundId( uint256 _rewardEpochId ) external view returns ( uint32 ); ``` ### getThreshold Returns the threshold for given reward epoch id. ```solidity function getThreshold( uint256 _rewardEpochId ) external view returns ( uint16 ); ``` ### getVotePowerBlock Returns the vote power block for given reward epoch id. ```solidity function getVotePowerBlock( uint256 _rewardEpochId ) external view returns ( uint64 _votePowerBlock ); ``` ### getVoterRegistrationData Returns voter rgistration data for given reward epoch id. ```solidity function getVoterRegistrationData( uint256 _rewardEpochId ) external view returns ( uint256 _votePowerBlock, bool _enabled ); ``` #### Parameters - `_rewardEpochId`: Reward epoch id. #### Returns - `_votePowerBlock`: Vote power block. - `_enabled`: Indicates if voter registration is enabled. ### isVoterRegistrationEnabled Indicates if voter registration is currently enabled. ```solidity function isVoterRegistrationEnabled( ) external view returns ( bool ); ``` ### rewardEpochDurationSeconds Duration of reward epoch, in seconds. ```solidity function rewardEpochDurationSeconds( ) external view returns ( uint64 ); ``` ### signNewSigningPolicy Method for collecting signatures for the new signing policy. ```solidity function signNewSigningPolicy( uint24 _rewardEpochId, bytes32 _newSigningPolicyHash, struct IFlareSystemsManager.Signature _signature ) external; ``` #### Parameters - `_rewardEpochId`: Reward epoch id of the new signing policy. - `_newSigningPolicyHash`: New signing policy hash. - `_signature`: Signature. ### signRewards Method for collecting signatures for the rewards. ```solidity function signRewards( uint24 _rewardEpochId, struct IFlareSystemsManager.NumberOfWeightBasedClaims[] _noOfWeightBasedClaims, bytes32 _rewardsHash, struct IFlareSystemsManager.Signature _signature ) external; ``` #### Parameters - `_rewardEpochId`: Reward epoch id of the rewards. - `_noOfWeightBasedClaims`: Number of weight based claims list. - `_rewardsHash`: Rewards hash. - `_signature`: Signature. ### signUptimeVote Method for collecting signatures for the uptime vote. ```solidity function signUptimeVote( uint24 _rewardEpochId, bytes32 _uptimeVoteHash, struct IFlareSystemsManager.Signature _signature ) external; ``` #### Parameters - `_rewardEpochId`: Reward epoch id of the uptime vote. - `_uptimeVoteHash`: Uptime vote hash. - `_signature`: Signature. ### submitUptimeVote Method for submitting node ids with high enough uptime. ```solidity function submitUptimeVote( uint24 _rewardEpochId, bytes20[] _nodeIds, struct IFlareSystemsManager.Signature _signature ) external; ``` #### Parameters - `_rewardEpochId`: Reward epoch id of the uptime vote. - `_nodeIds`: Node ids with high enough uptime. - `_signature`: Signature. ### votingEpochDurationSeconds Duration of voting epoch, in seconds. ```solidity function votingEpochDurationSeconds( ) external view returns ( uint64 ); ``` ## Events ### RandomAcquisitionStarted Event emitted when random acquisition phase starts. ```solidity event RandomAcquisitionStarted( uint24 rewardEpochId, uint64 timestamp ) ``` ### RewardEpochStarted Event emitted when reward epoch starts. ```solidity event RewardEpochStarted( uint24 rewardEpochId, uint32 startVotingRoundId, uint64 timestamp ) ``` ### RewardsSigned Event emitted when rewards are signed. ```solidity event RewardsSigned( uint24 rewardEpochId, address signingPolicyAddress, address voter, bytes32 rewardsHash, struct IFlareSystemsManager.NumberOfWeightBasedClaims[] noOfWeightBasedClaims, uint64 timestamp, bool thresholdReached ) ``` ### SignUptimeVoteEnabled Event emitted when it is time to sign uptime vote. ```solidity event SignUptimeVoteEnabled( uint24 rewardEpochId, uint64 timestamp ) ``` ### SigningPolicySigned Event emitted when signing policy is signed. ```solidity event SigningPolicySigned( uint24 rewardEpochId, address signingPolicyAddress, address voter, uint64 timestamp, bool thresholdReached ) ``` ### UptimeVoteSigned Event emitted when uptime vote is signed. ```solidity event UptimeVoteSigned( uint24 rewardEpochId, address signingPolicyAddress, address voter, bytes32 uptimeVoteHash, uint64 timestamp, bool thresholdReached ) ``` ### UptimeVoteSubmitted Event emitted when uptime vote is submitted. ```solidity event UptimeVoteSubmitted( uint24 rewardEpochId, address signingPolicyAddress, address voter, bytes20[] nodeIds, uint64 timestamp ) ``` ### VotePowerBlockSelected Event emitted when vote power block is selected. ```solidity event VotePowerBlockSelected( uint24 rewardEpochId, uint64 votePowerBlock, uint64 timestamp ) ``` ## Structures ### NumberOfWeightBasedClaims Number of weight based claims structure ```solidity struct NumberOfWeightBasedClaims { uint256 rewardManagerId; uint256 noOfWeightBasedClaims; } ``` ### Signature Signature structure ```solidity struct Signature { uint8 v; bytes32 r; bytes32 s; } ``` --- ## IRelay Stores confirmed Merkle roots and signing policies. Sourced from `IRelay.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IRelay.sol). ## Functions ### feeCollectionAddress Returns fee collection address. ```solidity function feeCollectionAddress( ) external view returns ( address payable ); ``` ### getRandomNumber Returns the current random number, its timestamp and the flag indicating if it is secure. ```solidity function getRandomNumber( ) external view returns ( uint256 _randomNumber, bool _isSecureRandom, uint256 _randomTimestamp ); ``` #### Returns - `_randomNumber`: The current random number. - `_isSecureRandom`: The flag indicating if the random number is secure. - `_randomTimestamp`: The timestamp of the random number. ### getRandomNumberHistorical Returns the historical random number for a given \_votingRoundId, its timestamp and the flag indicating if it is secure. If no finalization in the \_votingRoundId, the function reverts. ```solidity function getRandomNumberHistorical( uint256 _votingRoundId ) external view returns ( uint256 _randomNumber, bool _isSecureRandom, uint256 _randomTimestamp ); ``` #### Parameters - `_votingRoundId`: The voting round id. #### Returns - `_randomNumber`: The current random number. - `_isSecureRandom`: The flag indicating if the random number is secure. - `_randomTimestamp`: The timestamp of the random number. ### getVotingRoundId Returns the voting round id for given timestamp. ```solidity function getVotingRoundId( uint256 _timestamp ) external view returns ( uint256 _votingRoundId ); ``` #### Parameters - `_timestamp`: The timestamp. #### Returns - `_votingRoundId`: The voting round id. ### governanceFeeSetup Checks the relay message for sufficient weight of signatures of the hash of the \_config data. If the check is successful, the relay contract is configured with the new \_config data, which in particular means that fee configurations are updated. Otherwise the function reverts. ```solidity function governanceFeeSetup( bytes _relayMessage, struct IRelay.RelayGovernanceConfig _config ) external; ``` #### Parameters - `_relayMessage`: The relay message. - `_config`: The new relay configuration. ### isFinalized Returns true if there is finalization for a given protocol id and voting round id. ```solidity function isFinalized( uint256 _protocolId, uint256 _votingRoundId ) external view returns ( bool ); ``` #### Parameters - `_protocolId`: The protocol id. - `_votingRoundId`: The voting round id. ### lastInitializedRewardEpochData Returns last initialized reward epoch data. ```solidity function lastInitializedRewardEpochData( ) external view returns ( uint32 _lastInitializedRewardEpoch, uint32 _startingVotingRoundIdForLastInitializedRewardEpoch ); ``` #### Returns - `_lastInitializedRewardEpoch`: Last initialized reward epoch. - `_startingVotingRoundIdForLastInitializedRewardEpoch`: Starting voting round id for it. ### merkleRoots Returns the Merkle root for given protocol id and voting round id. The function is reverted if signingPolicySetter is set, hence on all deployments where the contract is used as a pure relay. ```solidity function merkleRoots( uint256 _protocolId, uint256 _votingRoundId ) external view returns ( bytes32 _merkleRoot ); ``` #### Parameters - `_protocolId`: The protocol id. - `_votingRoundId`: The voting round id. #### Returns - `_merkleRoot`: The Merkle root. ### protocolFeeInWei Returns fee in wei for one verification of a given protocol id. ```solidity function protocolFeeInWei( uint256 _protocolId ) external view returns ( uint256 ); ``` #### Parameters - `_protocolId`: The protocol id. ### relay Finalization function for new signing policies and protocol messages. It can be used as finalization contract on Flare chain or as relay contract on other EVM chain. Can be called in two modes. It expects calldata that is parsed in a custom manner. Hence the transaction calls should assemble relevant calldata in the 'data' field. Depending on the data provided, the contract operations in essentially two modes: (1) Relaying signing policy. The structure of the calldata is: function signature (4 bytes) + active signing policy + 0 (1 byte) + new signing policy, total of exactly 4423 bytes. (2) Relaying signed message. The structure of the calldata is: function signature (4 bytes) + signing policy + signed message (38 bytes) + ECDSA signatures with indices (67 bytes each) This case splits into two subcases: - protocolMessageId = 1: Message id must be of the form (protocolMessageId, 0, 0, merkleRoot). The validity of the signatures of sufficient weight is checked and if successful, the merkleRoot from the message is returned (32 bytes) and the reward epoch id of the signing policy as well (additional 3 bytes) - protocolMessageId > 1: The validity of the signatures of sufficient weight is checked and if it is valid, the merkleRoot is published for protocolId and votingRoundId. Reverts if relaying is not successful. ```solidity function relay( ) external returns ( bytes ); ``` ### startingVotingRoundIds Returns the start voting round id for given reward epoch id. ```solidity function startingVotingRoundIds( uint256 _rewardEpochId ) external view returns ( uint256 _startingVotingRoundId ); ``` #### Parameters - `_rewardEpochId`: The reward epoch id. #### Returns - `_startingVotingRoundId`: The start voting round id. ### toSigningPolicyHash Returns the signing policy hash for given reward epoch id. The function is reverted if signingPolicySetter is set, hence on all deployments where the contract is used as a pure relay. ```solidity function toSigningPolicyHash( uint256 _rewardEpochId ) external view returns ( bytes32 _signingPolicyHash ); ``` #### Parameters - `_rewardEpochId`: The reward epoch id. #### Returns - `_signingPolicyHash`: The signing policy hash. ### verify Verifies the leaf (or intermediate node) with the Merkle proof against the Merkle root for given protocol id and voting round id. A fee may need to be paid. It is protocol specific. **NOTE:** Overpayment is not refunded. ```solidity function verify( uint256 _protocolId, uint256 _votingRoundId, bytes32 _leaf, bytes32[] _proof ) external payable returns ( bool ); ``` #### Parameters - `_protocolId`: The protocol id. - `_votingRoundId`: The voting round id. - `_leaf`: The leaf (or intermediate node) to verify. - `_proof`: The Merkle proof. #### Returns - ``: True if the verification is successful. ### verifyCustomSignature Checks the relay message for sufficient weight of signatures for the \_messageHash signed for protocol message Merkle root of the form (1, 0, 0, \_messageHash). If the check is successful, reward epoch id of the signing policy is returned. Otherwise the function reverts. ```solidity function verifyCustomSignature( bytes _relayMessage, bytes32 _messageHash ) external returns ( uint256 _rewardEpochId ); ``` #### Parameters - `_relayMessage`: The relay message. - `_messageHash`: The hash of the message. #### Returns - `_rewardEpochId`: The reward epoch id of the signing policy. ## Events ### ProtocolMessageRelayed ```solidity event ProtocolMessageRelayed( uint8 protocolId, uint32 votingRoundId, bool isSecureRandom, bytes32 merkleRoot ) ``` ### SigningPolicyInitialized ```solidity event SigningPolicyInitialized( uint24 rewardEpochId, uint32 startVotingRoundId, uint16 threshold, uint256 seed, address[] voters, uint16[] weights, bytes signingPolicyBytes, uint64 timestamp ) ``` ### SigningPolicyRelayed ```solidity event SigningPolicyRelayed( uint256 rewardEpochId ) ``` ## Structures ### FeeConfig ```solidity struct FeeConfig { uint8 protocolId; uint256 feeInWei; } ``` ### RelayGovernanceConfig ```solidity struct RelayGovernanceConfig { bytes32 descriptionHash; uint256 chainId; struct IRelay.FeeConfig[] newFeeConfigs; } ``` ### RelayInitialConfig ```solidity struct RelayInitialConfig { uint32 initialRewardEpochId; uint32 startingVotingRoundIdForInitialRewardEpochId; bytes32 initialSigningPolicyHash; uint8 randomNumberProtocolId; uint32 firstVotingRoundStartTs; uint8 votingEpochDurationSeconds; uint32 firstRewardEpochStartVotingRoundId; uint16 rewardEpochDurationInVotingEpochs; uint16 thresholdIncreaseBIPS; uint32 messageFinalizationWindowInRewardEpochs; address payable feeCollectionAddress; struct IRelay.FeeConfig[] feeConfigs; } ``` --- ## IRewardManager Facilitates the claiming and distribution of rewards to voters, delegators, and stakers. Sourced from `IRewardManager.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IRewardManager.sol). ## Functions ### active Indicates if the contract is active - claims are enabled. ```solidity function active( ) external view returns ( bool ); ``` ### autoClaim Claim rewards for `_rewardOwners` and their PDAs. Rewards are deposited to the WNat (to reward owner or PDA if enabled). It can be called by reward owner or its authorized executor. Only claiming from weight based claims is supported. ```solidity function autoClaim( address[] _rewardOwners, uint24 _rewardEpochId, struct RewardsV2Interface.RewardClaimWithProof[] _proofs ) external; ``` #### Parameters - `_rewardOwners`: Array of reward owners. - `_rewardEpochId`: Id of the reward epoch up to which the rewards are claimed. - `_proofs`: Array of reward claims with merkle proofs. ### claim Claim rewards for `_rewardOwner` and transfer them to `_recipient`. It can be called by reward owner or its authorized executor. ```solidity function claim( address _rewardOwner, address payable _recipient, uint24 _rewardEpochId, bool _wrap, struct RewardsV2Interface.RewardClaimWithProof[] _proofs ) external returns ( uint256 _rewardAmountWei ); ``` #### Parameters - `_rewardOwner`: Address of the reward owner. - `_recipient`: Address of the reward recipient. - `_rewardEpochId`: Id of the reward epoch up to which the rewards are claimed. - `_wrap`: Indicates if the reward should be wrapped (deposited) to the WNat contract. - `_proofs`: Array of reward claims with merkle proofs. #### Returns - `_rewardAmountWei`: Amount of rewarded native tokens (wei). ### cleanupBlockNumber Get the current cleanup block number. ```solidity function cleanupBlockNumber( ) external view returns ( uint256 ); ``` #### Returns - ``: The currently set cleanup block number. ### firstClaimableRewardEpochId The first reward epoch id that was claimable. ```solidity function firstClaimableRewardEpochId( ) external view returns ( uint24 ); ``` ### getCurrentRewardEpochId Returns current reward epoch id. ```solidity function getCurrentRewardEpochId( ) external view returns ( uint24 ); ``` ### getInitialRewardEpochId Returns initial reward epoch id. ```solidity function getInitialRewardEpochId( ) external view returns ( uint256 ); ``` ### getNextClaimableRewardEpochId Returns the next claimable reward epoch for a reward owner. ```solidity function getNextClaimableRewardEpochId( address _rewardOwner ) external view returns ( uint256 ); ``` #### Parameters - `_rewardOwner`: Address of the reward owner to query. ### getRewardEpochIdToExpireNext Returns the reward epoch id that will expire next once a new reward epoch starts. ```solidity function getRewardEpochIdToExpireNext( ) external view returns ( uint256 ); ``` ### getRewardEpochIdsWithClaimableRewards Returns the start and the end of the reward epoch range for which the reward is claimable. ```solidity function getRewardEpochIdsWithClaimableRewards( ) external view returns ( uint24 _startEpochId, uint24 _endEpochId ); ``` #### Returns - `_startEpochId`: The oldest epoch id that allows reward claiming. - `_endEpochId`: The newest epoch id that allows reward claiming. ### getRewardEpochTotals Returns reward epoch totals. ```solidity function getRewardEpochTotals( uint24 _rewardEpochId ) external view returns ( uint256 _totalRewardsWei, uint256 _totalInflationRewardsWei, uint256 _initialisedRewardsWei, uint256 _claimedRewardsWei, uint256 _burnedRewardsWei ); ``` #### Parameters - `_rewardEpochId`: Reward epoch id. #### Returns - `_totalRewardsWei`: Total rewards (inflation + community) for the epoch (wei). - `_totalInflationRewardsWei`: Total inflation rewards for the epoch (wei). - `_initialisedRewardsWei`: Initialised rewards of all claim types for the epoch (wei). - `_claimedRewardsWei`: Claimed rewards for the epoch (wei). - `_burnedRewardsWei`: Burned rewards for the epoch (wei). ### getStateOfRewards Returns the state of rewards for a given address for all unclaimed reward epochs with claimable rewards. ```solidity function getStateOfRewards( address _rewardOwner ) external view returns ( struct RewardsV2Interface.RewardState[][] _rewardStates ); ``` #### Parameters - `_rewardOwner`: Address of the reward owner. #### Returns - `_rewardStates`: Array of reward states. ### getStateOfRewardsAt Returns the state of rewards for a given address at a specific reward epoch. ```solidity function getStateOfRewardsAt( address _rewardOwner, uint24 _rewardEpochId ) external view returns ( struct RewardsV2Interface.RewardState[] _rewardStates ); ``` #### Parameters - `_rewardOwner`: Address of the reward owner. - `_rewardEpochId`: Reward epoch id. #### Returns - `_rewardStates`: Array of reward states. ### getTotals Returns totals. ```solidity function getTotals( ) external view returns ( uint256 _totalRewardsWei, uint256 _totalInflationRewardsWei, uint256 _totalClaimedWei, uint256 _totalBurnedWei ); ``` #### Returns - `_totalRewardsWei`: Total rewards (wei). - `_totalInflationRewardsWei`: Total inflation rewards (wei). - `_totalClaimedWei`: Total claimed rewards (wei). - `_totalBurnedWei`: Total burned rewards (wei). ### getUnclaimedRewardState Gets the unclaimed reward state for a beneficiary, reward epoch id and claim type. ```solidity function getUnclaimedRewardState( address _beneficiary, uint24 _rewardEpochId, enum RewardsV2Interface.ClaimType _claimType ) external view returns ( struct IRewardManager.UnclaimedRewardState _state ); ``` #### Parameters - `_beneficiary`: Address of the beneficiary to query. - `_rewardEpochId`: Id of the reward epoch to query. - `_claimType`: Claim type to query. #### Returns - `_state`: Unclaimed reward state. ### initialiseWeightBasedClaims Initialises weight based claims. ```solidity function initialiseWeightBasedClaims( struct RewardsV2Interface.RewardClaimWithProof[] _proofs ) external; ``` #### Parameters - `_proofs`: Array of reward claims with merkle proofs. ### noOfInitialisedWeightBasedClaims Returns the number of weight based claims that have been initialised. ```solidity function noOfInitialisedWeightBasedClaims( uint256 _rewardEpochId ) external view returns ( uint256 ); ``` #### Parameters - `_rewardEpochId`: Reward epoch id. ## Events ### RewardClaimed Emitted when rewards are claimed. ```solidity event RewardClaimed( address beneficiary, address rewardOwner, address recipient, uint24 rewardEpochId, enum RewardsV2Interface.ClaimType claimType, uint120 amount ) ``` #### Parameters - `beneficiary`: Address of the beneficiary (voter or node id) that accrued the reward. - `rewardOwner`: Address that was eligible for the rewards. - `recipient`: Address that received the reward. - `rewardEpochId`: Id of the reward epoch where the reward was accrued. - `claimType`: Claim type - `amount`: Amount of rewarded native tokens (wei). ### RewardClaimsEnabled Emitted when reward claims have been enabled. ```solidity event RewardClaimsEnabled( uint256 rewardEpochId ) ``` #### Parameters - `rewardEpochId`: First claimable reward epoch. ### RewardClaimsExpired Unclaimed rewards have expired and are now inaccessible. `getUnclaimedRewardState()` can be used to retrieve more information. ```solidity event RewardClaimsExpired( uint256 rewardEpochId ) ``` #### Parameters - `rewardEpochId`: Id of the reward epoch that has just expired. ## Structures ### RewardClaim Struct used in Merkle tree for storing reward claims. ```solidity struct RewardClaim { uint24 rewardEpochId; bytes20 beneficiary; uint120 amount; enum RewardsV2Interface.ClaimType claimType; } ``` ### RewardClaimWithProof Struct used for claiming rewards with Merkle proof. ```solidity struct RewardClaimWithProof { bytes32[] merkleProof; struct RewardsV2Interface.RewardClaim body; } ``` ### RewardState Struct used for returning state of rewards. ```solidity struct RewardState { uint24 rewardEpochId; bytes20 beneficiary; uint120 amount; enum RewardsV2Interface.ClaimType claimType; bool initialised; } ``` ### UnclaimedRewardState Struct used for storing unclaimed reward data. ```solidity struct UnclaimedRewardState { bool initialised; uint120 amount; uint128 weight; } ``` --- ## ISubmission Manages prioritized and subsidized submissions for protocols. Sourced from `ISubmission.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/ISubmission.sol). The `getCurrentRandom*` methods are inherited from [`IRandomProvider`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IRandomProvider.sol). For most consumer use cases prefer the dedicated [`RandomNumberV2Interface`](/network/solidity-reference/RandomNumberV2Interface) (resolved from `IFlareContractRegistry` as `"RandomNumberV2"`) rather than calling `getCurrentRandom` on `ISubmission` directly. ## Functions ### getCurrentRandom Returns current random number. Method reverts if random number was not generated securely. ```solidity function getCurrentRandom( ) external view returns ( uint256 _randomNumber ); ``` #### Returns - `_randomNumber`: Current random number. ### getCurrentRandomWithQuality Returns current random number and a flag indicating if it was securely generated. It is up to the caller to decide whether to use the returned random number or not. ```solidity function getCurrentRandomWithQuality( ) external view returns ( uint256 _randomNumber, bool _isSecureRandom ); ``` #### Returns - `_randomNumber`: Current random number. - `_isSecureRandom`: Indicates if current random number is secure. ### getCurrentRandomWithQualityAndTimestamp Returns current random number, a flag indicating if it was securely generated and its timestamp. It is up to the caller to decide whether to use the returned random number or not. ```solidity function getCurrentRandomWithQualityAndTimestamp( ) external view returns ( uint256 _randomNumber, bool _isSecureRandom, uint256 _randomTimestamp ); ``` #### Returns - `_randomNumber`: Current random number. - `_isSecureRandom`: Indicates if current random number is secure. - `_randomTimestamp`: Random timestamp. ### submit1 Submit1 method. Used in multiple protocols (i.e. as FTSO commit method). ```solidity function submit1( ) external returns ( bool ); ``` ### submit2 Submit2 method. Used in multiple protocols (i.e. as FTSO reveal method). ```solidity function submit2( ) external returns ( bool ); ``` ### submit3 Submit3 method. Future usage. ```solidity function submit3( ) external returns ( bool ); ``` ### submitAndPass SubmitAndPass method. Future usage. ```solidity function submitAndPass( bytes _data ) external returns ( bool ); ``` #### Parameters - `_data`: The data to pass to the submitAndPassContract. ### submitSignatures SubmitSignatures method. Used in multiple protocols (i.e. as FTSO submit signature method). ```solidity function submitSignatures( ) external returns ( bool ); ``` ## Events ### NewVotingRoundInitiated Event emitted when a new voting round is initiated. ```solidity event NewVotingRoundInitiated( ) ``` --- ## IVoterRegistry Manages the registration of voters for upcoming reward epochs. Sourced from `IVoterRegistry.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IVoterRegistry.sol). ## Functions ### chilledUntilRewardEpochId In case of providing bad votes (e.g. ftso collusion), the beneficiary can be chilled for a few reward epochs. If beneficiary is chilled, the vote power assigned to it is zero. ```solidity function chilledUntilRewardEpochId( bytes20 _beneficiary ) external view returns ( uint256 _rewardEpochId ); ``` #### Parameters - `_beneficiary`: The beneficiary (c-chain address or node id). #### Returns - `_rewardEpochId`: The reward epoch id until which the voter is chilled. ### getNumberOfRegisteredVoters Returns the number of registered voters for a given reward epoch. Size can be zero if the reward epoch is not supported (before initial reward epoch or future reward epoch). Size for the next reward epoch can still change until the signing policy snapshot is created. ```solidity function getNumberOfRegisteredVoters( uint256 _rewardEpochId ) external view returns ( uint256 ); ``` #### Parameters - `_rewardEpochId`: The reward epoch id. ### getRegisteredVoters Returns the list of registered voters for a given reward epoch. List can be empty if the reward epoch is not supported (before initial reward epoch or future reward epoch). List for the next reward epoch can still change until the signing policy snapshot is created. ```solidity function getRegisteredVoters( uint256 _rewardEpochId ) external view returns ( address[] ); ``` #### Parameters - `_rewardEpochId`: The reward epoch id. ### isVoterRegistered Returns true if a voter was (is currently) registered in a given reward epoch. ```solidity function isVoterRegistered( address _voter, uint256 _rewardEpochId ) external view returns ( bool ); ``` #### Parameters - `_voter`: The voter address. - `_rewardEpochId`: The reward epoch id. ### maxVoters Maximum number of voters in one reward epoch. ```solidity function maxVoters( ) external view returns ( uint256 ); ``` ### newSigningPolicyInitializationStartBlockNumber Returns the block number of the start of the new signing policy initialisation for a given reward epoch. It is a snapshot block of the voters' addresses (it is zero if the reward epoch is not supported). ```solidity function newSigningPolicyInitializationStartBlockNumber( uint256 _rewardEpochId ) external view returns ( uint256 ); ``` #### Parameters - `_rewardEpochId`: The reward epoch id. ### publicKeyRequired Indicates if the voter must have the public key set when registering. ```solidity function publicKeyRequired( ) external view returns ( bool ); ``` ### registerVoter Registers a voter if the weight is high enough. ```solidity function registerVoter( address _voter, struct IVoterRegistry.Signature _signature ) external; ``` #### Parameters - `_voter`: The voter address. - `_signature`: The signature. ## Events ### BeneficiaryChilled Event emitted when a beneficiary (c-chain address or node id) is chilled. ```solidity event BeneficiaryChilled( bytes20 beneficiary, uint256 untilRewardEpochId ) ``` ### VoterRegistered Event emitted when a voter is registered. ```solidity event VoterRegistered( address voter, uint24 rewardEpochId, address signingPolicyAddress, address submitAddress, address submitSignaturesAddress, bytes32 publicKeyPart1, bytes32 publicKeyPart2, uint256 registrationWeight ) ``` ### VoterRemoved Event emitted when a voter is removed. ```solidity event VoterRemoved( address voter, uint256 rewardEpochId ) ``` ## Structures ### Signature Signature data. ```solidity struct Signature { uint8 v; bytes32 r; bytes32 s; } ``` --- ## IWNatDelegationFee Manages the delegation fees set by voters for WFLR delegations. Sourced from `IWNatDelegationFee.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IWNatDelegationFee.sol). ## Functions ### defaultFeePercentageBIPS The default fee percentage value. ```solidity function defaultFeePercentageBIPS( ) external view returns ( uint16 ); ``` ### feePercentageUpdateOffset The offset in reward epochs for the fee percentage value to become effective. ```solidity function feePercentageUpdateOffset( ) external view returns ( uint24 ); ``` ### getVoterCurrentFeePercentage Returns the current fee percentage of `_voter`. ```solidity function getVoterCurrentFeePercentage( address _voter ) external view returns ( uint16 ); ``` #### Parameters - `_voter`: Voter address. ### getVoterFeePercentage Returns the fee percentage of `_voter` for given reward epoch id. ```solidity function getVoterFeePercentage( address _voter, uint256 _rewardEpochId ) external view returns ( uint16 ); ``` #### Parameters - `_voter`: Voter address. - `_rewardEpochId`: Reward epoch id. **NOTE:** fee percentage might still change for the `current + feePercentageUpdateOffset` reward epoch id ### getVoterScheduledFeePercentageChanges Returns the scheduled fee percentage changes of `_voter`. ```solidity function getVoterScheduledFeePercentageChanges( address _voter ) external view returns ( uint256[] _feePercentageBIPS, uint256[] _validFromEpochId, bool[] _fixed ); ``` #### Parameters - `_voter`: Voter address. #### Returns - `_feePercentageBIPS`: Positional array of fee percentages in BIPS. - `_validFromEpochId`: Positional array of reward epoch ids the fee settings are effective from. - `_fixed`: Positional array of boolean values indicating if settings are subjected to change. ### setVoterFeePercentage Allows voter to set (or update last) fee percentage. ```solidity function setVoterFeePercentage( uint16 _feePercentageBIPS ) external returns ( uint256 ); ``` #### Parameters - `_feePercentageBIPS`: Number representing fee percentage in BIPS. #### Returns - ``: Returns the reward epoch number when the value becomes effective. ## Events ### FeePercentageChanged Event emitted when a voter fee percentage value is changed. ```solidity event FeePercentageChanged( address voter, uint16 value, uint24 validFromEpochId ) ``` --- ## IClaimSetupManager Interface for managing reward claim setup. Sourced from `IClaimSetupManager.sol` on [GitLab](https://gitlab.com/flarenetwork/flare-smart-contracts/-/blob/master/contracts/userInterfaces/IClaimSetupManager.sol). ## Functions ### setAutoClaiming Sets the addresses of executors and optionally enables (creates) delegation account. If setting registered executors some fee must be paid to them. ```solidity function setAutoClaiming( address[] _executors, bool _enableDelegationAccount ) external payable; ``` #### Parameters - `_executors`: The new executors. All old executors will be deleted and replaced by these. - `_enableDelegationAccount`: ### setClaimExecutors Sets the addresses of executors. If setting registered executors some fee must be paid to them. ```solidity function setClaimExecutors( address[] _executors ) external payable; ``` #### Parameters - `_executors`: The new executors. All old executors will be deleted and replaced by these. ### setAllowedClaimRecipients Set the addresses of allowed recipients. Apart from these, the owner is always an allowed recipient. ```solidity function setAllowedClaimRecipients( address[] _recipients ) external; ``` #### Parameters - `_recipients`: The new allowed recipients. All old recipients will be deleted and replaced by these. ### enableDelegationAccount Enables (creates) delegation account contract, i.e. all airdrop and ftso rewards will be send to delegation account when using automatic claiming. ```solidity function enableDelegationAccount( ) external returns ( contract IDelegationAccount); ``` #### Returns - ``: Address of delegation account contract. ### disableDelegationAccount Disables delegation account contract, i.e. all airdrop and ftso rewards will be send to owner's account when using automatic claiming. Automatic claiming will not claim airdrop and ftso rewards for delegation account anymore. ```solidity function disableDelegationAccount( ) external; ``` ### registerExecutor Allows executor to register and set initial fee value. If executor was already registered before (has fee set), only update fee after `feeValueUpdateOffset`. Executor must pay fee in order to register - `registerExecutorFeeValueWei`. ```solidity function registerExecutor( uint256 _feeValue ) external payable returns ( uint256); ``` #### Parameters - `_feeValue`: number representing fee value #### Returns - ``: Returns the reward epoch number when the setting becomes effective. ### unregisterExecutor Allows executor to unregister. ```solidity function unregisterExecutor( ) external returns ( uint256); ``` #### Returns - ``: Returns the reward epoch number when the setting becomes effective. ### updateExecutorFeeValue Allows registered executor to set (or update last scheduled) fee value. ```solidity function updateExecutorFeeValue( uint256 _feeValue ) external returns ( uint256); ``` #### Parameters - `_feeValue`: number representing fee value #### Returns - ``: Returns the reward epoch number when the setting becomes effective. ### delegate Delegate `_bips` of voting power to `_to` from msg.sender's delegation account ```solidity function delegate( address _to, uint256 _bips ) external; ``` #### Parameters - `_to`: The address of the recipient - `_bips`: The percentage of voting power to be delegated expressed in basis points (1/100 of one percent). Not cumulative - every call resets the delegation value (and value of 0 revokes delegation). ### batchDelegate Undelegate all percentage delegations from the msg.sender's delegation account and then delegate corresponding `_bips` percentage of voting power to each member of `_delegatees`. ```solidity function batchDelegate( address[] _delegatees, uint256[] _bips ) external; ``` #### Parameters - `_delegatees`: The addresses of the new recipients. - `_bips`: The percentages of voting power to be delegated expressed in basis points (1/100 of one percent). Total of all `_bips` values must be at most 10000. ### undelegateAll Undelegate all voting power for delegates of msg.sender's delegation account ```solidity function undelegateAll( ) external; ``` ### revokeDelegationAt Revoke all delegation from msg.sender's delegation account to `_who` at given block. Only affects the reads via `votePowerOfAtCached()` in the block `_blockNumber`. Block `_blockNumber` must be in the past. This method should be used only to prevent rogue delegate voting in the current voting block. To stop delegating use delegate with value of 0 or undelegateAll. ```solidity function revokeDelegationAt( address _who, uint256 _blockNumber ) external; ``` ### delegateGovernance Delegate all governance vote power of msg.sender's delegation account to `_to`. ```solidity function delegateGovernance( address _to ) external; ``` #### Parameters - `_to`: The address of the recipient ### undelegateGovernance Undelegate governance vote power for delegate of msg.sender's delegation account ```solidity function undelegateGovernance( ) external; ``` ### withdraw Allows user to transfer WNat to owner's account. ```solidity function withdraw( uint256 _amount ) external; ``` #### Parameters - `_amount`: Amount of tokens to transfer ### transferExternalToken Allows user to transfer balance of ERC20 tokens owned by the personal delegation contract. The main use case is to transfer tokens/NFTs that were received as part of an airdrop or register as participant in such airdrop. ```solidity function transferExternalToken( contract IERC20 _token, uint256 _amount ) external; ``` #### Parameters - `_token`: Target token contract address - `_amount`: Amount of tokens to transfer ### accountToDelegationAccount Gets the delegation account of the `_owner`. Returns address(0) if not created yet. ```solidity function accountToDelegationAccount( address _owner ) external view returns ( address); ``` ### getDelegationAccountData Gets the delegation account data for the `_owner`. Returns address(0) if not created yet. ```solidity function getDelegationAccountData( address _owner ) external view returns ( contract IDelegationAccount _delegationAccount, bool _enabled); ``` #### Parameters - `_owner`: owner's address #### Returns - `_delegationAccount`: owner's delegation account address - could be address(0) - `_enabled`: indicates if delegation account is enabled ### claimExecutors Get the addresses of executors. ```solidity function claimExecutors( address _owner ) external view returns ( address[]); ``` ### allowedClaimRecipients Get the addresses of allowed recipients. Apart from these, the owner is always an allowed recipient. ```solidity function allowedClaimRecipients( address _rewardOwner ) external view returns ( address[]); ``` ### isClaimExecutor Returns info if `_executor` is allowed to execute calls for `_owner` ```solidity function isClaimExecutor( address _owner, address _executor ) external view returns ( bool); ``` ### getRegisteredExecutors Get registered executors ```solidity function getRegisteredExecutors( uint256 _start, uint256 _end ) external view returns ( address[] _registeredExecutors, uint256 _totalLength); ``` ### getExecutorInfo Returns some info about the `_executor` ```solidity function getExecutorInfo( address _executor ) external view returns ( bool _registered, uint256 _currentFeeValue); ``` #### Parameters - `_executor`: address representing executor #### Returns - `_registered`: information if executor is registered - `_currentFeeValue`: executor's current fee value ### getExecutorCurrentFeeValue Returns the current fee value of `_executor` ```solidity function getExecutorCurrentFeeValue( address _executor ) external view returns ( uint256); ``` #### Parameters - `_executor`: address representing executor ### getExecutorFeeValue Returns the fee value of `_executor` at `_rewardEpoch` ```solidity function getExecutorFeeValue( address _executor, uint256 _rewardEpoch ) external view returns ( uint256); ``` #### Parameters - `_executor`: address representing executor - `_rewardEpoch`: reward epoch number ### getExecutorScheduledFeeValueChanges Returns the scheduled fee value changes of `_executor` ```solidity function getExecutorScheduledFeeValueChanges( address _executor ) external view returns ( uint256[] _feeValue, uint256[] _validFromEpoch, bool[] _fixed); ``` #### Parameters - `_executor`: address representing executor #### Returns - `_feeValue`: positional array of fee values - `_validFromEpoch`: positional array of reward epochs the fee settings are effective from - `_fixed`: positional array of boolean values indicating if settings are subjected to change ## Events ### DelegationAccountCreated ```solidity event DelegationAccountCreated( address owner, contract IDelegationAccount delegationAccount ) ``` ### DelegationAccountUpdated ```solidity event DelegationAccountUpdated( address owner, contract IDelegationAccount delegationAccount, bool enabled ) ``` ### ClaimExecutorsChanged ```solidity event ClaimExecutorsChanged( address owner, address[] executors ) ``` ### AllowedClaimRecipientsChanged ```solidity event AllowedClaimRecipientsChanged( address owner, address[] recipients ) ``` ### ClaimExecutorFeeValueChanged ```solidity event ClaimExecutorFeeValueChanged( address executor, uint256 validFromRewardEpoch, uint256 feeValueWei ) ``` ### ExecutorRegistered ```solidity event ExecutorRegistered( address executor ) ``` ### ExecutorUnregistered ```solidity event ExecutorUnregistered( address executor, uint256 validFromRewardEpoch ) ``` ### MinFeeSet ```solidity event MinFeeSet( uint256 minFeeValueWei ) ``` ### MaxFeeSet ```solidity event MaxFeeSet( uint256 maxFeeValueWei ) ``` ### RegisterExecutorFeeSet ```solidity event RegisterExecutorFeeSet( uint256 registerExecutorFeeValueWei ) ``` ### SetExecutorsExcessAmountRefunded ```solidity event SetExecutorsExcessAmountRefunded( address owner, uint256 excessAmount ) ``` --- ## IFlareContractRegistry Registry interface with all Flare contract addresses. Check out the [guide](/network/guides/flare-contracts-registry) for instructions on how to interact with the Flare contracts registry. Sourced from `IFlareContractRegistry.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFlareContractRegistry.sol). ## Functions ### getContractAddressByName Returns contract address for the given name - might be address(0). ```solidity function getContractAddressByName( string _name ) external view returns ( address); ``` #### Parameters - `_name`: name of the contract which is **case-sensitive**, so you should use the proper capitalization. ### getContractAddressByHash Returns contract address for the given name hash - might be address(0). ```solidity function getContractAddressByHash( bytes32 _nameHash ) external view returns ( address); ``` #### Parameters - `_nameHash`: hash of the contract name (keccak256(abi.encode(name)) ### getContractAddressesByName Returns contract addresses for the given names - might be address(0). ```solidity function getContractAddressesByName( string[] _names ) external view returns ( address[]); ``` #### Parameters - `_names`: names of the contracts which is **case-sensitive**, so you should use the proper capitalization. ### getContractAddressesByHash Returns contract addresses for the given name hashes - might be address(0) ```solidity function getContractAddressesByHash( bytes32[] _nameHashes ) external view returns ( address[]); ``` #### Parameters - `_nameHashes`: hashes of the contract names (keccak256(abi.encode(name)) ### getAllContracts Returns all contract names and corresponding addresses ```solidity function getAllContracts( ) external view returns ( string[] _names, address[] _addresses); ``` --- ## IRNat Interface for managing rFLR. Sourced from `IRNat.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IRNat.sol). ## Functions ### allowance Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through transferFrom. This is zero by default. This value changes when approve or transferFrom are called. ```solidity function allowance( address owner, address spender ) external view returns ( uint256 ); ``` ### approve Sets a `value` amount of tokens as the allowance of `spender` over the caller's tokens. Returns a boolean value indicating whether the operation succeeded. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an Approval event. ```solidity function approve( address spender, uint256 value ) external returns ( bool ); ``` ### balanceOf Returns the value of tokens owned by `account`. ```solidity function balanceOf( address account ) external view returns ( uint256 ); ``` ### claimRewards Claim rewards for a list of projects up to the given month. ```solidity function claimRewards( uint256[] _projectIds, uint256 _month ) external returns ( uint128 _claimedRewardsWei ); ``` #### Parameters - `_projectIds`: The ids of the projects. - `_month`: The month up to which (including) rewards will be claimed. #### Returns - `_claimedRewardsWei`: The total amount of rewards claimed (in wei). ### decimals Returns the decimals places of the token. ```solidity function decimals( ) external view returns ( uint8 ); ``` ### distributeRewards Distributes the rewards of a project for a given month to a list of recipients. It must be called by the project's distributor. It can only be called for the last or current month (if enabled). ```solidity function distributeRewards( uint256 _projectId, uint256 _month, address[] _recipients, uint128[] _amountsWei ) external; ``` #### Parameters - `_projectId`: The id of the project. - `_month`: The month of the rewards. - `_recipients`: The addresses of the recipients. - `_amountsWei`: The amounts of rewards to distribute to each recipient (in wei). ### firstMonthStartTs Returns the timestamp of the start of the first month. ```solidity function firstMonthStartTs( ) external view returns ( uint256 ); ``` ### getBalancesOf Gets owner's balances of `WNat`, `RNat` and locked tokens. ```solidity function getBalancesOf( address _owner ) external view returns ( uint256 _wNatBalance, uint256 _rNatBalance, uint256 _lockedBalance ); ``` #### Parameters - `_owner`: The address of the owner. #### Returns - `_wNatBalance`: The balance of `WNat` (in wei). - `_rNatBalance`: The balance of `RNat` (in wei). - `_lockedBalance`: The locked/vested balance (in wei). ### getClaimableRewards Gets the claimable rewards of a project for a given owner. ```solidity function getClaimableRewards( uint256 _projectId, address _owner ) external view returns ( uint128 ); ``` #### Parameters - `_projectId`: The id of the project. - `_owner`: The address of the owner. #### Returns - ``: The amount of rewards claimable by the owner (in wei). ### getCurrentMonth Gets the current month. ```solidity function getCurrentMonth( ) external view returns ( uint256 ); ``` #### Returns - ``: The current month. ### getOwnerRewardsInfo Gets the rewards information of a project for a given month and owner. ```solidity function getOwnerRewardsInfo( uint256 _projectId, uint256 _month, address _owner ) external view returns ( uint128 _assignedRewards, uint128 _claimedRewards, bool _claimable ); ``` #### Parameters - `_projectId`: The id of the project. - `_month`: The month of the rewards. - `_owner`: The address of the owner. #### Returns - `_assignedRewards`: The amount of rewards assigned to the owner for the month (in wei). - `_claimedRewards`: The amount of rewards claimed by the owner for the month (in wei). - `_claimable`: Whether the rewards are claimable by the owner. ### getProjectInfo Gets the information of a project. ```solidity function getProjectInfo( uint256 _projectId ) external view returns ( string _name, address _distributor, bool _currentMonthDistributionEnabled, bool _distributionDisabled, bool _claimingDisabled, uint128 _totalAssignedRewards, uint128 _totalDistributedRewards, uint128 _totalClaimedRewards, uint128 _totalUnassignedUnclaimedRewards, uint256[] _monthsWithRewards ); ``` #### Parameters - `_projectId`: The id of the project. #### Returns - `_name`: The name of the project. - `_distributor`: The address of the distributor. - `_currentMonthDistributionEnabled`: Whether distribution is enabled for the current month. - `_distributionDisabled`: Whether distribution is disabled. - `_claimingDisabled`: Whether claiming is disabled. - `_totalAssignedRewards`: The total amount of rewards assigned to the project (in wei). - `_totalDistributedRewards`: The total amount of rewards distributed by the project (in wei). - `_totalClaimedRewards`: The total amount of rewards claimed from the project (in wei). - `_totalUnassignedUnclaimedRewards`: The total amount of unassigned unclaimed rewards (in wei). - `_monthsWithRewards`: The months with rewards. ### getProjectRewardsInfo Gets the rewards information of a project for a given month. ```solidity function getProjectRewardsInfo( uint256 _projectId, uint256 _month ) external view returns ( uint128 _assignedRewards, uint128 _distributedRewards, uint128 _claimedRewards, uint128 _unassignedUnclaimedRewards ); ``` #### Parameters - `_projectId`: The id of the project. - `_month`: The month of the rewards. #### Returns - `_assignedRewards`: The amount of rewards assigned to the project for the month (in wei). - `_distributedRewards`: The amount of rewards distributed by the project for the month (in wei). - `_claimedRewards`: The amount of rewards claimed from the project for the month (in wei). - `_unassignedUnclaimedRewards`: The amount of unassigned unclaimed rewards for the month (in wei). ### getProjectsBasicInfo Gets the basic information of all projects. ```solidity function getProjectsBasicInfo( ) external view returns ( string[] _names, bool[] _claimingDisabled ); ``` #### Returns - `_names`: The names of the projects. - `_claimingDisabled`: Whether claiming is disabled for each project. ### getProjectsCount Gets the total number of projects. ```solidity function getProjectsCount( ) external view returns ( uint256 ); ``` #### Returns - ``: The total number of projects. ### getRNatAccount Gets owner's RNat account. If it doesn't exist it reverts. ```solidity function getRNatAccount( address _owner ) external view returns ( contract IRNatAccount ); ``` #### Parameters - `_owner`: Account to query. #### Returns - ``: Address of its RNat account. ### getRewardsInfo Gets totals rewards information. ```solidity function getRewardsInfo( ) external view returns ( uint256 _totalAssignableRewards, uint256 _totalAssignedRewards, uint256 _totalClaimedRewards, uint256 _totalWithdrawnRewards, uint256 _totalWithdrawnAssignableRewards ); ``` #### Returns - `_totalAssignableRewards`: The total amount of assignable rewards (in wei). - `_totalAssignedRewards`: The total amount of assigned rewards (in wei). - `_totalClaimedRewards`: The total amount of claimed rewards (in wei). - `_totalWithdrawnRewards`: The total amount of withdrawn rewards (in wei). - `_totalWithdrawnAssignableRewards`: The total amount of withdrawn once assignable rewards (in wei). ### name Returns the name of the token. ```solidity function name( ) external view returns ( string ); ``` ### setClaimExecutors Sets the addresses of executors and adds the owner as an executor. If any of the executors is a registered executor, some fee needs to be paid. ```solidity function setClaimExecutors( address[] _executors ) external payable; ``` #### Parameters - `_executors`: The new executors. All old executors will be deleted and replaced by these. ### symbol Returns the symbol of the token. ```solidity function symbol( ) external view returns ( string ); ``` ### totalSupply Returns the value of tokens in existence. ```solidity function totalSupply( ) external view returns ( uint256 ); ``` ### transfer Moves a `value` amount of tokens from the caller's account to `to`. Returns a boolean value indicating whether the operation succeeded. Emits a Transfer event. ```solidity function transfer( address to, uint256 value ) external returns ( bool ); ``` ### transferExternalToken Allows the caller to transfer ERC-20 tokens from their RNat account to the owner account. The main use case is to move ERC-20 tokes received by mistake (by an airdrop, for example) out of the RNat account and move them into the main account, where they can be more easily managed. Reverts if the target token is the `WNat` contract: use method `withdraw` or `withdrawAll` for that. ```solidity function transferExternalToken( contract IERC20 _token, uint256 _amount ) external; ``` #### Parameters - `_token`: Target token contract address. - `_amount`: Amount of tokens to transfer. ### transferFrom Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism. `value` is then deducted from the caller's allowance. Returns a boolean value indicating whether the operation succeeded. Emits a Transfer event. ```solidity function transferFrom( address from, address to, uint256 value ) external returns ( bool ); ``` ### WNat Returns the `WNat` contract. ```solidity function wNat( ) external view returns ( contract IWNat ); ``` ### withdraw Allows the caller to withdraw `WNat` wrapped tokens from their RNat account to the owner account. In case there are some self-destruct native tokens left on the contract, they can be transferred to the owner account using this method and `_wrap = false`. ```solidity function withdraw( uint128 _amount, bool _wrap ) external; ``` #### Parameters - `_amount`: Amount of tokens to transfer (in wei). - `_wrap`: If `true`, the tokens will be sent wrapped in `WNat`. If `false`, they will be sent as `Nat`. ### withdrawAll Allows the caller to withdraw `WNat` wrapped tokens from their RNat account to the owner account. If some tokens are still locked, only 50% of them will be withdrawn, the rest will be burned as a penalty. In case there are some self-destruct native tokens left on the contract, they can be transferred to the owner account using this method and `_wrap = false`. ```solidity function withdrawAll( bool _wrap ) external; ``` #### Parameters - `_wrap`: If `true`, the tokens will be sent wrapped in `WNat`. If `false`, they will be sent as `Nat`. ## Events ### Approval Emitted when the allowance of a `spender` for an `owner` is set by a call to approve. `value` is the new allowance. ```solidity event Approval( address owner, address spender, uint256 value ) ``` ### ClaimingPermissionUpdated ```solidity event ClaimingPermissionUpdated( uint256[] projectIds, bool disabled ) ``` ### DistributionPermissionUpdated ```solidity event DistributionPermissionUpdated( uint256[] projectIds, bool disabled ) ``` ### ProjectAdded ```solidity event ProjectAdded( uint256 id, string name, address distributor, bool currentMonthDistributionEnabled ) ``` ### ProjectUpdated ```solidity event ProjectUpdated( uint256 id, string name, address distributor, bool currentMonthDistributionEnabled ) ``` ### RNatAccountCreated ```solidity event RNatAccountCreated( address owner, contract IRNatAccount rNatAccount ) ``` ### RewardsAssigned ```solidity event RewardsAssigned( uint256 projectId, uint256 month, uint128 amount ) ``` ### RewardsClaimed ```solidity event RewardsClaimed( uint256 projectId, uint256 month, address owner, uint128 amount ) ``` ### RewardsDistributed ```solidity event RewardsDistributed( uint256 projectId, uint256 month, address[] recipients, uint128[] amounts ) ``` ### RewardsUnassigned ```solidity event RewardsUnassigned( uint256 projectId, uint256 month, uint128 amount ) ``` ### Transfer Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero. ```solidity event Transfer( address from, address to, uint256 value ) ``` ### UnassignedRewardsWithdrawn ```solidity event UnassignedRewardsWithdrawn( address recipient, uint128 amount ) ``` ### UnclaimedRewardsUnassigned ```solidity event UnclaimedRewardsUnassigned( uint256 projectId, uint256 month, uint128 amount ) ``` --- ## IWNat Interface for wrapping and unwrapping the native Flare token (FLR/SGB/C2FLR/CFLR) into the ERC-20 representation WNAT. Sourced from `IWNat.sol` in [`@flarenetwork/flare-periphery-contracts`](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) (v0.1.41+). The published `IWNat` interface only declares the four functions below. The ERC-20 surface area (`balanceOf`, `transfer`, `approve`, `allowance`, ...) is inherited via the WNat implementation contract, not the `IWNat` interface; if you need those methods, import [`IVPToken`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IVPToken.sol) (which extends `IERC20`) instead. Vote-power and delegation methods (`delegate`, `governanceVotePower`, ...) come from `IVPToken` + [`IGovernanceVotePower`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IGovernanceVotePower.sol). ## Functions ### deposit Deposit native token and mint WNAT ERC-20 to the caller. ```solidity function deposit() external payable; ``` ### withdraw Withdraw native token and burn WNAT ERC-20 from the caller. ```solidity function withdraw(uint256 _amount) external; ``` Parameters: - `_amount`: The amount to withdraw. ### depositTo Deposit native token from `msg.sender` and mint WNAT ERC-20 to `_recipient`. ```solidity function depositTo(address _recipient) external payable; ``` Parameters: - `_recipient`: The address that receives the minted WNAT. ### withdrawFrom Withdraw WNAT from `_owner` (subject to allowance) and send the native token to `msg.sender`. ```solidity function withdrawFrom(address _owner, uint256 _amount) external; ``` Parameters: - `_owner`: An address spending the native tokens. - `_amount`: The amount to spend. Requirements: - `_owner` must have a balance of at least `_amount`. - The caller must have an allowance for `_owner`'s tokens of at least `_amount`. ## Related interfaces - [`IVPToken`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IVPToken.sol) — ERC-20 + vote-power view methods inherited by the WNat implementation. - [`IGovernanceVotePower`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IGovernanceVotePower.sol) — governance vote-power delegation methods. - [`IClaimSetupManager`](/network/solidity-reference/IClaimSetupManager) — claim executor configuration that builds on top of WNAT. --- ## ProtocolsV2Interface Primary interface for managing protocol related metadata. This is a long-term support (LTS) interface, designed to ensure continuity even as underlying contracts evolve or protocols migrate to new versions. Sourced from `ProtocolsV2Interface.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/LTS/ProtocolsV2Interface.sol). ## Functions ### firstRewardEpochStartTs Timestamp when the first reward epoch started, in seconds since UNIX epoch. ```solidity function firstRewardEpochStartTs( ) external view returns ( uint64 ); ``` ### firstVotingRoundStartTs Timestamp when the first voting epoch started, in seconds since UNIX epoch. ```solidity function firstVotingRoundStartTs( ) external view returns ( uint64 ); ``` ### getCurrentRewardEpochId Returns the current reward epoch id. ```solidity function getCurrentRewardEpochId( ) external view returns ( uint24 ); ``` ### getCurrentVotingEpochId Returns the current voting epoch id. ```solidity function getCurrentVotingEpochId( ) external view returns ( uint32 ); ``` ### getStartVotingRoundId Returns the start voting round id for given reward epoch id. ```solidity function getStartVotingRoundId( uint256 _rewardEpochId ) external view returns ( uint32 ); ``` ### getVotePowerBlock Returns the vote power block for given reward epoch id. ```solidity function getVotePowerBlock( uint256 _rewardEpochId ) external view returns ( uint64 _votePowerBlock ); ``` ### rewardEpochDurationSeconds Duration of reward epoch, in seconds. ```solidity function rewardEpochDurationSeconds( ) external view returns ( uint64 ); ``` ### votingEpochDurationSeconds Duration of voting epoch, in seconds. ```solidity function votingEpochDurationSeconds( ) external view returns ( uint64 ); ``` --- ## RandomNumberV2Interface Primary interface for random number generation. This is a long-term support (LTS) interface, designed to ensure continuity even as underlying contracts evolve or protocols migrate to new versions. Sourced from `RandomNumberV2Interface.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/LTS/RandomNumberV2Interface.sol). ## Functions ### getRandomNumber Returns the current random number, its timestamp and the flag indicating if it is secure. ```solidity function getRandomNumber( ) external view returns ( uint256 _randomNumber, bool _isSecureRandom, uint256 _randomTimestamp ); ``` #### Returns - `_randomNumber`: The current random number. - `_isSecureRandom`: The flag indicating if the random number is secure. - `_randomTimestamp`: The timestamp of the random number. ### getRandomNumberHistorical Returns the historical random number for a given \_votingRoundId, its timestamp and the flag indicating if it is secure. If no finalization in the \_votingRoundId, the function reverts. ```solidity function getRandomNumberHistorical( uint256 _votingRoundId ) external view returns ( uint256 _randomNumber, bool _isSecureRandom, uint256 _randomTimestamp ); ``` #### Parameters - `_votingRoundId`: The voting round id. #### Returns - `_randomNumber`: The current random number. - `_isSecureRandom`: The flag indicating if the random number is secure. - `_randomTimestamp`: The timestamp of the random number. --- ## RewardsV2Interface Primary interface for managing all protocol rewards. This is a long-term support (LTS) interface, designed to ensure continuity even as underlying contracts evolve or protocols migrate to new versions. Sourced from `RewardsV2Interface.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/LTS/RewardsV2Interface.sol). ## Functions ### active Indicates if the contract is active - claims are enabled. ```solidity function active( ) external view returns ( bool ); ``` ### claim Claim rewards for `_rewardOwner` and transfer them to `_recipient`. It can be called by reward owner or its authorized executor. ```solidity function claim( address _rewardOwner, address payable _recipient, uint24 _rewardEpochId, bool _wrap, struct RewardsV2Interface.RewardClaimWithProof[] _proofs ) external returns ( uint256 _rewardAmountWei ); ``` #### Parameters - `_rewardOwner`: Address of the reward owner. - `_recipient`: Address of the reward recipient. - `_rewardEpochId`: Id of the reward epoch up to which the rewards are claimed. - `_wrap`: Indicates if the reward should be wrapped (deposited) to the WNat contract. - `_proofs`: Array of reward claims with merkle proofs. #### Returns - `_rewardAmountWei`: Amount of rewarded native tokens (wei). ### getNextClaimableRewardEpochId Returns the next claimable reward epoch for a reward owner. ```solidity function getNextClaimableRewardEpochId( address _rewardOwner ) external view returns ( uint256 ); ``` #### Parameters - `_rewardOwner`: Address of the reward owner to query. ### getRewardEpochIdsWithClaimableRewards Returns the start and the end of the reward epoch range for which the reward is claimable. ```solidity function getRewardEpochIdsWithClaimableRewards( ) external view returns ( uint24 _startEpochId, uint24 _endEpochId ); ``` #### Returns - `_startEpochId`: The oldest epoch id that allows reward claiming. - `_endEpochId`: The newest epoch id that allows reward claiming. ### getStateOfRewards Returns the state of rewards for a given address for all unclaimed reward epochs with claimable rewards. ```solidity function getStateOfRewards( address _rewardOwner ) external view returns ( struct RewardsV2Interface.RewardState[][] _rewardStates ); ``` #### Parameters - `_rewardOwner`: Address of the reward owner. #### Returns - `_rewardStates`: Array of reward states. ## Structures ### RewardClaim Struct used in Merkle tree for storing reward claims. ```solidity struct RewardClaim { uint24 rewardEpochId; bytes20 beneficiary; uint120 amount; enum RewardsV2Interface.ClaimType claimType; } ``` ### RewardClaimWithProof Struct used for claiming rewards with Merkle proof. ```solidity struct RewardClaimWithProof { bytes32[] merkleProof; struct RewardsV2Interface.RewardClaim body; } ``` ### RewardState Struct used for returning state of rewards. ```solidity struct RewardState { uint24 rewardEpochId; bytes20 beneficiary; uint120 amount; enum RewardsV2Interface.ClaimType claimType; bool initialised; } ``` ## Enums ### ClaimType Claim type enum. ```solidity enum ClaimType { DIRECT, FEE, WNAT, MIRROR, CCHAIN } ``` --- ## FTSOv2 The **F**lare **T**ime **S**eries **O**racle **(FTSO)** is an [enshrined oracle](/support/terminology#enshrined-oracle) that provides decentralized data feeds to the Flare network. Since the release of FTSOv1, users and applications on Flare have enjoyed consistent and reliable pricing. FTSOv2 builds on the robust foundation laid by its predecessor, offering several enhancements: - **Secure.** Enshrined into Flare's core protocol, every oracle feed in FTSOv2 inherits the economic security of the entire network. - **Fast.** FTSOv2 features block-latency feeds, updating with each new block on Flare, every ≈1.8 seconds. - **Scalable.** FTSOv2 supports up to 1000 feeds across various asset classes including equities, commodities, and cryptocurrencies, with access to 2 weeks of historical data. - **Decentralized.** Each FTSOv2 feed is supported by around 100 independent data providers, who are selected by Flare users through their delegated stake, imposing a strict economic cost for misbehavior. - **Cost-effective.** Block-latency feeds in FTSOv2 are free to use onchain. Feeds from Scaling are also free to query and verify locally, with minimal gas costs for onchain verification. ## Architecture FTSOv2 ensures fast, secure, and manipulation-resistant feeds by using a stake-weighted verifiable randomness function (VRF) to select a sample of data providers for incremental delta updates. These updates maintain long-term accuracy by anchoring to Scaling feeds, which use a full commit-reveal process and update every 90 seconds. During high market volatility, volatility incentives can increase the sample size of data providers for a quicker response to market conditions. The FTSOv2 architecture consists of four key components: 1. **Verifiably Random Selection:** Each block on Flare triggers the selection of data providers to deliver the next feed update using a stake-weighted Verifiable Randomness Function. This ensures fairness and resistance to manipulation. 2. **Incremental Delta Update:** Selected data providers submit new feed updates as fixed incremental deltas applied to the previous feed value. This maintains reliable and continuous updates, ensuring integrity and accuracy. 3. **Volatility Incentive Mechanism:** To handle periods of high market volatility, FTSOv2 introduces volatility incentives, temporarily increasing the sample size of selected data providers in exchange for a fee. This permissionless mechanism ensures a faster response to significant price movements. 4. **Anchoring to Scaling Feeds:** Scaling feeds, which employ a full commit-reveal process across all data providers, act as anchors to ensure long-term accuracy. :::tip[Interested in learning more?] For a detailed explanation of the FTSOv2 mechanism, read the [FTSOv2 whitepaper](https://dev.flare.network/pdf/whitepapers/20240223-FlareTimeSeriesOracleV2.pdf). ::: ### Verifiably Random Selection Every block on Flare, generated approximately every 1.8 seconds, initiates the selection of a sample of data providers to deliver the next feed update. This selection process leverages a stake-weighted verifiable randomness function (VRF), where the likelihood of each data provider being chosen is proportional to their stake. The expected sample size (average) is `1.5`, and data providers have no control over, nor knowledge of, when they will be selected. In detail, each block has a unique seed value, used by FTSOv2 data providers to generate a personal random score. This score, coupled with a cryptographic proof, ensures its authenticity and verifiability. Data providers are chosen based on their scores: those with scores below a certain threshold are selected to make updates. The selection probability is proportional to the data provider's stake, allowing for weighted sampling where participants with a higher stake have a greater chance of being selected. To maintain security, the seed value itself evolves pseudo-randomly. This approach balances security and randomness, preventing adversaries from influencing the selection process. The system is designed to be statistically robust, ensuring a reliable and continuous selection of participants to uphold the integrity and accuracy of updates. ### Incremental Delta Update The selected data providers submit the new feed update, which is a fixed incremental delta applied to the previous feed value. The base increment size for all updates is `1/2^13 ≈ 0.0122%`, a value determined through extensive market analysis and approved by Flare governance. The delta can be in one of three directions: - **Up (+)**: The new feed value is incrementally increased from the previous value. - **Down (–)**: The new feed value is incrementally decreased from the previous value. - **Unchanged (0)**: The new feed value remains the same as the previous value.
The mathematics of incremental delta updates. The FTSOv2 protocol utilizes block-latency feeds, denoted as $P(t)$, which are continuously updated following the formula, $$ P(t + 1) = (1 + p)^{\delta(t)}P(t) $$ where, - $p$ is the protocol parameter named _precision_, and - $\delta$ is the update provided by a data provider, with $\delta(t)$ being one of the three options $\{-1, 0, 1\}$. The precision parameter is set to a default value of $p = 1/2^{13} ≈ 0.0122\%$, which has been rigorously tested against price feeds from centralized exchanges. Another key protocol parameter is the average number of updates submitted per block, with a default value of $e = 1$. The average number of updates submitted per block $e$ can be temporarily increased in exchange for a fee using the volatility incentive mechanism.
### Volatility Incentive Mechanism From statistical analysis, FTSOv2's mechanism is capable of capturing over 99% of all price movements under normal market conditions. However, during periods of high market volatility, the small size of each increment may be slower to reflect large price movements. To address this, FTSOv2 introduces volatility incentives, which allows for a temporary increase in the sample size of data providers in exchange for a fee. The volatility incentive mechanism is permissionless, enabling anyone on Flare to trigger it by paying the required fee. Typically, the expected sample size is one. With volatility incentives, this sample size is temporarily increased, allowing for more updates and quicker responses to large price movements. Importantly, only the expected sample size increases, not the actual sample size, which further helps protect FTSOv2 against various statistical attacks. ### Anchoring to Scaling FTSOv2's block-latency feeds are designed to be statistically self-correcting. To further ensure their long-term accuracy, FTSOv2 uses the anchor feeds from [Scaling](/ftso/scaling/overview). Anchor feeds utilize a full commit-reveal process across all data providers with an inter-quartile range (IQR) band calculation, and update once every voting epoch (i.e. 90 seconds). Data providers are rewarded when the block-latency feeds remain within `±0.25%` of the anchor feeds every voting epoch. ## Watch the video --- ## Getting Started(Ftso) You can use FTSOv2 to connect your smart contracts with real-world data feeds. FTSOv2 leverages Flare's network of 100 independent data providers to fetch offchain data and deliver it onchain. This section demonstrates how to consume FTSOv2's block-latency feeds on Flare using an onchain Solidity contract. :::info[New to smart contract development?] Learn how to [deploy your first smart contract](/network/getting-started) before you start this guide. ::: ## Sample contract This example smart contract queries the latest feed values for FLR/USD, BTC/USD, and ETH/USD from FTSOv2 on Flare Testnet Coston2. {FtsoV2FeedConsumer} {/* prettier-ignore */} Open in Remix :::warning[Don't use test interfaces in production] The `TestFtsoV2Interface` is for **testing only**, with all methods as `view` to allow rapid development without gas costs or state changes. For production, use [`FtsoV2Interface`](/ftso/solidity-reference/FtsoV2Interface), which includes `payable` methods required for real transactions and state modifications. Make the following changes to `FtsoV2FeedConsumer.sol`: ```solidity contract FtsoV2FeedConsumer { //... function getFtsoV2CurrentFeedValues() external returns ( uint256[] memory _feedValues, int8[] memory _decimals, uint64 _timestamp ) { ftsoV2 = ContractRegistry.getFtsoV2(); /* Your custom feed consumption logic. In this example the values are just returned. */ return ftsoV2.getFeedsById(feedIds); } } ``` :::
Breaking down the contract. - **Purpose**: Interacts with the Flare Network to fetch current feed values for specific cryptocurrency pairs (FLR/USD, BTC/USD, ETH/USD). - **Dependencies**: - `ContractRegistry.sol`: Used to get the addresses of various contracts on the Flare network. - `TestFtsoV2Interface.sol`: This interface allows interaction with the FTSOv2 contract, which provides real-time price feeds for various assets. - **State Variables**: - `ftsoV2`: This is a state variable of type `TestFtsoV2Interface`. It will hold the address of the FTSO V2 contract once initialized. - `feedIndexes`: An array of indexes corresponding to different price feeds. In this example: - Index 0 corresponds to FLR/USD - Index 2 corresponds to BTC/USD - Index 9 corresponds to ETH/USD - **Constructor**: - The constructor is a special function that runs only once when the contract is deployed. It initializes the `ftsoV2` state variable by fetching the FTSO V2 contract address using the `ContractRegistry`. - **Function `getFtsoV2CurrentFeedValues`**: - It is marked as `external`, meaning it can be called from outside the contract. - It is also marked as `payable`, allowing it to receive Ether when called, though in this example, the Ether is not used. - It returns three values: - `_feedValues`: The latest price values for the specified feeds. - `_decimals`: The decimal precision of each feed value. - `_timestamp`: The timestamp when the prices were fetched. Inside the function: - The `ftsoV2.getFeedsByIndex(feedIndexes)` call retrieves the latest prices for the indexes specified in `feedIndexes`. - These values are then returned to the caller.
## Compile and deploy the contract :::info[Configure and fund your wallet] If you have not already configured your MetaMask wallet to support Flare Testnet Coston2 and funded it with testnet C2FLR, learn how to [deploy your first smart contract](/network/getting-started). You can get testnet C2FLR from the [Coston2 Faucet](https://faucet.flare.network/coston2). ::: {/* prettier-ignore */} 1. Open contract in Remix 2. Click on `FtsoV2FeedConsumer.sol` in the file explorer to open the contract in the editor. 2. On the left side of Remix, click the **Solidity Compiler** tab to view the compiler settings. 3. Expand the **Advanced Configurations** section and make sure the **EVM Version** is set to `cancun`. 3. Click the **Compile FtsoV2FeedConsumer.sol** button to compile the contract. 4. On the left side of Remix, click the **Deploy & Run transactions** tab to view the deployment settings. 5. Select the **Injected Provider - MetaMask** environment. 6. Click **Deploy** to deploy the contract to Flare Testnet Coston2. MetaMask opens and asks you to confirm payment for deploying the contract. Make sure MetaMask is set to Flare Testnet Coston2 before you confirm the transaction. 7. In the MetaMask prompt, click **Confirm** to approve the transaction and spend your testnet C2FLR required to deploy the contract. 8. After a few seconds, the transaction completes and your contract appears under the **Deployed/Unpinned Contracts** list in Remix. Click the contract dropdown to view its variables and functions. Click on **getFtsoV2CurrentFeedValues** to show the latest feed values and decimals. #### Function Returns - `_feedValues`: Current integer values of FLR/USD, BTC/USD, and ETH/USD. The returns are in the same order as the input `feedIndexes` array. - `_decimals`: Decimal places for FLR/USD, BTC/USD, and ETH/USD. - `_timestamp`: Timestamp of the last feed update. The floating point value of a feed can be calculated by dividing the `feedValue` 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`. :::tip[What's next?] [Build your first FTSOv2 app](/ftso/guides/build-first-app) using Foundry, or [read feeds offchain](/ftso/guides/read-feeds-offchain) in languages like JavaScript, Python, Rust, and Go. ::: ## Watch the video --- ## Block-Latency Feeds FTSOv2's block-latency feeds update incrementally with each new block on Flare, approximately every 1.8 seconds. Every feed leverages Flare's network of 100 independent data providers. These feeds primarily support cryptocurrency price data and are free to query on Flare, enabling decentralized applications to access up-to-date information without incurring additional costs.
How Feed IDs are derived. Each block-latency feed is uniquely identified by an ID composed of three components in a structured encoding process: 1. **Category:** Indicates the type of asset - Crypto: `01`, Forex: `02`, Commodity: `03`, Stock: `04`, Custom Feeds: `21` 2. **Hex-Encoded Feed Name:** The name of the feed is converted to a hexadecimal format. 3. **Zero Padding:** The combined category and hex-encoded feed name are padded with zeros to a length of 21 bytes. 4. **0x-prefix:** The resulting string is then prefixed with `0x`. As an example, take the feed name `FLR/USD`: - **Category**: `01` (Crypto) - **Hex-Encoded Feed Name**: `464c522f555344` (hexadecimal representation of `FLR/USD`) - **Zero Padding**: The category and hex-encoded feed name combined and padded: `01464c522f55534400000000000000000000000000` - **Final ID**: Adding the `0x` prefix results in `0x01464c522f55534400000000000000000000000000` {GetFeedIdSol} {GetFeedIdJS} {GetFeedIdPy} {GetFeedIdRs} {GetFeedIdGo}
🚦 Understanding feed risks. When building applications, developers must carefully evaluate the quality of the data they use. As a developer, you are responsible for identifying and assessing the accuracy, availability, and overall quality of the data you choose to integrate. It is important to understand that all data feeds carry inherent risks. The table below categorizes feeds into three risk levels based on their market integrity, ranging from lowest to highest, a feed is assigned a higher risk level if it fails to meet **all** the criteria required for classification within a lower risk level: | **Aspect** | 🟢 **Low Risk** | 🟡 **Medium Risk** | 🔴 **High Risk** | | --------------------------- | ----------------------------------------- | ------------------------------------------- | ------------------------------------------------------ | | **Intrinsic Volatility** | Low, stable price trends | Moderate price fluctuations | High, frequent price swings | | **Liquidity Variation** | Abundant and consistent | Sufficient but variable | Limited and inconsistent | | **Liquidity Concentration** | Broad and well-distributed across venues | Somewhat concentrated | Highly concentrated in a few sources | | **Asset Spread Risk** | Tight spreads, minimal bid-ask gaps | Moderate spreads, acceptable bid-ask gaps | Wide spreads, significant bid-ask gaps | | **Cross Rate Risk** | Low correlation, direct pricing available | Moderate correlation, indirect pricing used | High correlation, dependent on multiple intermediaries | **Other risk categories:** - ⚫ **New Feed** New tokens lack historical data for proper risk assessment and stable price discovery. Users must recognize these assets carry higher volatility risks and verify feed reliability independently. After a monitoring period, feeds will be assigned risk levels or potentially discontinued. Each feed undergoes a rigorous assessment process prior to deployment. The evaluation criteria may vary depending on the specific type of feed being implemented and can evolve over time as our understanding of market integrity risks improves.
:::warning - Feed IDs are **not** addresses. They are `bytes21` structured encodings that combine the category and feed name to ensure each feed has a unique identifier. - **Do not** hardcode the number of decimals for a feed, as these can change. You can either check the number of decimal places every query using [`getFeedById`](/ftso/solidity-reference/FtsoV2Interface#getfeedbyid), or use the feed value in Wei using [`getFeedByIdInWei`](/ftso/solidity-reference/FtsoV2Interface#getfeedbyidinwei). ::: ## Custom Feeds :::warning[Risk Profile] Each Custom Feed has a unique risk profile determined by its smart contract and data source, which users and developers must assess individually. ::: Custom Feeds, introduced in [FIP.13](https://proposals.flare.network/FIP/FIP_13.html), extend the FTSO by enabling developers to create onchain feeds for arbitrary time-series data. 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. 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. :::tip[Create a new Custom Feed] Follow the [create a Custom Feed](/ftso/guides/create-custom-feed) guide to learn how a build a new Custom Feed. ::: ## Need more feeds? FTSOv2 can scale up to 1000 feeds. If you need additional FTSOv2 feeds beyond what is currently available, you can raise a New Feed Request Issue on GitHub. When a feed request is submitted, it is reviewed by the FTSO Management Group, which is comprised of the FTSO data providers as outlined in [FIP.08](https://proposals.flare.network/FIP/FIP_8.html#222-through-the-ftso-management-group). {/* prettier-ignore */} Request New Feed :::tip[What's next?] [Build your first FTSOv2 app](/ftso/guides/build-first-app) using Foundry, or [read feeds offchain](/ftso/guides/read-feeds-offchain) in languages like JavaScript, Python, Rust, and Go. ::: --- ## Feed Stability & Risks :::tip Last Updated Stability and risk data is updated every ~2 weeks. The metrics below reflect the most recent reporting window. ::: FTSOv2 block-latency feeds are continuously monitored for accuracy and stability. This page defines how the feed stability metrics are calculated and provides the latest analytics data for all block-latency feeds. ## Feed Stability Metrics **Feed stability** is the percentage of observations where a block-latency feed stays within a specified deviation band of a reference price. This metric helps users understand the reliability and accuracy of each feed over time. ### Metric definition Stability is calculated by comparing on-chain data against external reference sources (such as aggregated CEX prices) at specific observation times (`t`). - **Onchain Price (`P_onchain`)**: The price delivered by FTSOv2 Fast Updates. - **Reference Price (`P_ref`)**: The benchmark price from external sources. - **Deviation**: Calculated as the absolute percentage difference `deviation(t) = abs(P_onchain(t) - P_ref(t)) / P_ref(t)` Stability is tracked against a **0.2% deviation band**, which is the primary metric displayed in the analytics table below. ### Calculation methodology For a specific feed and deviation band `B`: 1. Collect time-aligned observations of `(P_onchain(t), P_ref(t))` over the reporting window. 2. Mark each observation as “in band” if `deviation(t) <= B`. 3. Compute stability as: `stability(B) = (in_band_observations / total_observations) * 100` :::info Stability measures _frequency_ (how often the price is accurate). It does not measure the _magnitude_ of the error when the price is inaccurate. ::: ### Interpreting stability scores | Stability Range | Status | Description | | --------------- | ------------ | ------------------------------------------------------------------------ | | ≥99% | 🟢 Excellent | Feed maintains exceptional accuracy relative to reference. | | 97-99% | 🟢 Good | Feed demonstrates strong accuracy with minimal deviation. | | 94-97% | 🟡 Moderate | Feed shows acceptable accuracy but may experience occasional deviations. | | 80-94% | 🔴 Low | Feed experiences notable deviations; consider additional validation. | | <80% | ⚫ Very Low | Feed shows significant instability; exercise caution when using. | ## Risk Classification Feeds are categorized into risk levels based on their underlying market integrity. **Assignment Logic**: This is a waterfall classification system. To qualify for a lower risk tier (e.g., Low Risk), a feed must meet **all** criteria in that column. If it fails even one criterion, it falls to the next risk level. | **Aspect** | 🟢 **Low Risk** | 🟡 **Medium Risk** | 🔴 **High Risk** | | --------------------------- | ----------------------------------------- | ------------------------------------------- | ------------------------------------------------------ | | **Intrinsic Volatility** | Low, stable price trends | Moderate price fluctuations | High, frequent price swings | | **Liquidity Variation** | Abundant and consistent | Sufficient but variable | Limited and inconsistent | | **Liquidity Concentration** | Broad and well-distributed across venues | Somewhat concentrated | Highly concentrated in a few sources | | **Asset Spread Risk** | Tight spreads, minimal bid-ask gaps | Moderate spreads, acceptable bid-ask gaps | Wide spreads, significant bid-ask gaps | | **Cross Rate Risk** | Low correlation, direct pricing available | Moderate correlation, indirect pricing used | High correlation, dependent on multiple intermediaries | **Other risk categories:** - ⚫ **New Feed** New tokens lack historical data for proper risk assessment and stable price discovery. Users must recognize these assets carry higher volatility risks and verify feed reliability independently. After a monitoring period, feeds will be assigned risk levels or potentially discontinued. Each feed undergoes a rigorous assessment process prior to deployment. The evaluation criteria may vary depending on the specific type of feed being implemented and can evolve over time as our understanding of market integrity risks improves. :::note Risk assessments are conducted prior to deployment and may be updated as market conditions or liquidity profiles change. ::: ## Current Feed Stability The table below displays latest stability metrics for all FTSOv2 block-latency feeds. - **Stability**: Percentage of observations within **0.2%** of the reference price. - **Status**: Based on the stability interpretation table above. --- ## FTSOv2 Reference ## Deployed Contracts export const contracts = [ "FtsoV2", "FeeCalculator", "FtsoFeedIdConverter", "FastUpdater", "FastUpdatesConfiguration", "FastUpdateIncentiveManager", ]; ## Interfaces --- ## Migrating from v1 This guide is for applications moving from FTSOv1 to FTSOv2. Briefly, FTSOv2 comprises of: - **[Block-Latency Feeds](/ftso/feeds)**: These feeds are updated with each new block, approximately every 1.8 seconds. They can be accessed through [FtsoV2Interface](/ftso/solidity-reference/FtsoV2Interface) and are available directly onchain. - **[Anchor Feeds](/ftso/scaling/anchor-feeds)**: These feeds are provided through [Scaling](/ftso/scaling/overview) with a latency of 90 seconds. Feeds can be verified using [FtsoV2Interface](/ftso/solidity-reference/FtsoV2Interface) but are not immediately available onchain. A key difference between the two is the introduction of a payment mechanism for data access. This system helps prevent unnecessary data requests and ensures sustainable funding. For more details, refer to the [`IFeeCalculator`](/ftso/solidity-reference/IFeeCalculator) contract, which calculates fees for data access using the [`calculateFeeByIds`](/ftso/solidity-reference/IFeeCalculator#calculatefeebyids) method. Additionally, a new Long Term Support (LTS) system has been launched to ensure continued access to essential data and metadata within the Flare ecosystem. A series of [LTS interfaces](#lts-interfaces) have been introduced, each aligned with a specific product in the Flare ecosystem. It is strongly recommended to use these LTS contracts for data access instead of querying individual contracts or interfaces, as they are designed for long-term stability, even as underlying protocols evolve or migrate. ## Deprecated contracts (v1) ### PriceSubmitter The `PriceSubmitter` contract (`IPriceSubmitter`) is being deprecated as part of the transition to the new version of the FTSO system. If you were using `PriceSubmitter` for the following purposes, here are the recommended alternatives: - **Accessing other important contracts**: Use the [`FlareContractRegistry`](/network/solidity-reference/IFlareContractRegistry) instead. - **Voting-related functionalities**: These are now integrated into the new FTSO system. - **Random number generation** (`getCurrentRandom` or `getRandom`, applicable to Flare only): Switch to the new [`RandomNumberV2Interface`](#lts-interfaces). The new protocol updates random numbers every 90 seconds (aligned with the voting epoch duration, as returned by `votingEpochDurationSeconds`). This setting is immutable but could change if the protocol configuration is updated. In contrast, the old protocol updated random numbers every 180 seconds. #### Required Changes: - Use `RandomNumberV2` instead of `PriceSubmitter` (ensure you update to the new contract address and interface). - **Method updates**: - `getCurrentRandom` → `getRandomNumber` - `getRandom(epochId)` → `getRandomNumberHistorical(epochId)` ### FTSO The legacy `FTSO` contract is being deprecated and replaced. A minimal proxy will be deployed at the same address to provide basic backward compatibility. The replacement will be [`FtsoProxy`](#ftsoproxy), which will respond to a limited subset of calls that the old `FTSO` contract handled. Although the addresses will change, the `FTSORegistry` will be updated to point to the new contract addresses. #### Recommended Changes: - **Random number retrieval**: Switch to the new [`RandomNumberV2Interface`](#lts-interfaces). The proxy contract will still return current and historical random numbers, but they will be uniform across all `FTSO` contracts, and randomness will be sourced from the new provider. - **Fetching the current price**: For methods like `getCurrentPrice`, `getCurrentPriceDetails`, or `getCurrentPriceWithDecimals`, switch to the [`FTSOv2Interface`](#lts-interfaces). While the proxy interface will continue to function temporarily, it will not receive future updates. - **Fetching historical prices**: For methods such as `getEpochPrice`, or retrieving prices for specific voters or trusted data providers, the proxy contract **WILL REVERT**. It is essential to migrate to the new `FTSOv2Interface` for these functionalities. ### FTSORegistry The `FTSORegistry` (`IFTSORegistry`) is being deprecated. While you can continue to use it temporarily with its backward-compatible methods, it is strongly recommended to transition to the new interfaces as soon as possible, as `FTSORegistry` will no longer be maintained in the future. #### Recommended Changes: - **Getting FTSOs**: The methods `getFtso`, `getFtsoBySymbol`, `getFtsoIndex`, `getFtsoSymbol`, and `getSupportedIndices/Symbols/Ftsos` are now deprecated. They will still return the proxy implementation of FTSO, but these proxies will not be maintained moving forward. It is recommended to update your code to use the [FTSOv2Interface](#lts-interfaces) for price retrieval. Additional details like decimals and timestamps for individual FTSOs are now considered deprecated and can be obtained by querying prices directly through `FTSOv2`. - **Reading prices** using methods such as `getCurrentPrice(_ftso_index/_symbol)` or their array and decimal implementations is also deprecated. Although these methods will still return correct values, they will not be updated in the future. Use the [FTSOv2Interface](#lts-interfaces) instead to ensure future compatibility. - **Getting supported FTSOs**: This function will return correct results for legacy price pairs but will not be updated for new pairs introduced in the FTSOv2 system. To retrieve information about available pairs in the new system, you should transition to the [FTSOv2Interface](#lts-interfaces) interface. - **Read prices directly from `FtsoV2Interface`**: This will give you up-to-date prices and more detailed information. Additionally, the `FtsoV2Interface` is part of the Long Term Support (LTS) system, ensuring it will be maintained for an extended period. - **New system indexing and ID scheme**: - The indexing and ID scheme has changed. Old indices are now invalid, so do not use the previous `getFeedByIndex` method with the old indices. - IDs in the new system are 21-byte values, formatted as `"${OLD_FEED_NAME}/USD"` and zero-padded. - Use the `getFeedById` method in the new interface to retrieve old prices with this format: `getFeedById(bytes21(bytes.concat(bytes1(1), bytes(string.concat(OLD_FEED_NAME, "/USD")))))`. ### FTSORewardManager The reward system is undergoing significant changes. While an implementation of the old `FTSORewardManager` contract is provided, it will only support the most basic claim types and require pre-provided reward proofs. #### Recommended Changes: - **Claiming rewards**: Switch to the new [`RewardsV2Interface`](#lts-interfaces). The proxy contract will still allow you to claim rewards from the FTSO system, but it will not support claiming fees for data providers. - **Claiming rewards when delegating by amount**: This feature is no longer supported in the new system. ## LTS interfaces The primary goal of the Long Term Support (LTS) interfaces is to offer a stable and reliable way to access essential data and metadata within the Flare ecosystem. These interfaces are designed for long-term maintenance, ensuring continuity even as underlying contracts evolve or protocols migrate to new versions. Each LTS interface is aligned with a specific product within the Flare ecosystem, providing consistency and ease of use over time. | **Interface** | **Contract registry name** | **Notes** | | ------------------------------------------------------------------------------ | -------------------------- | --------------------------------------------------------- | | [ProtocolsV2Interface](/network/solidity-reference/ProtocolsV2Interface) | `ProtocolsV2` | Primary interface for managing protocol related metadata. | | [RewardsV2Interface](/network/solidity-reference/RewardsV2Interface) | `RewardsV2` | Primary interface for managing all protocol rewards. | | [RandomNumberV2Interface](/network/solidity-reference/RandomNumberV2Interface) | `RandomNumberV2` | Primary interface for random number generation. | | [FtsoV2Interface](/ftso/solidity-reference/FtsoV2Interface) | `FtsoV2` | Primary interface for interacting with FTSOv2. | :::warning - **RandomNumberV2Interface:** In addition to providing random numbers, the new methods also return a `_isSecureRandom` flag. Learn more about this flag in the guide on [Secure Random Numbers](/network/guides/secure-random-numbers). - **FtsoV2Interface:** Provides access to fetching block-latency feeds onchain, and verifying anchor feeds onchain. You can retrieve feeds using the [`getFeedById`](/ftso/solidity-reference/FtsoV2Interface#getfeedbyid) or [`getFeedsById`](/ftso/solidity-reference/FtsoV2Interface#getfeedsbyid) methods. These methods are now payable, and while the current fee is set to `0`, it is advisable to use [`FeeCalculator`](/ftso/solidity-reference/IFeeCalculator) to calculate the fee and be prepared for potential future changes. ::: ## Migration proxies For the time being, a set of proxy contracts is provided to allow access to the old data and reward systems. :::danger[Do not use for new developments] These proxies offer a temporary solution to ensure that previously deployed contracts can continue functioning until they are fully updated. ::: ### FtsoProxy The `FtsoProxy` contract is designed to maintain backward compatibility with the old FTSO contract. While it will be deployed at different addresses from the original FTSO contract, the `FTSORegistry` will be updated to point to these new addresses.
Methods in `FtsoProxy` ```solidity title="FtsoProxy.sol" /** * Always return true, as the proxy is always active. */ function active() external pure returns (bool) { return true; } /** * Will return the current epoch id correctly as defined by FSP */ function getCurrentEpochId() external view returns (uint256) { } /** * Will return the epoch id correctly as defined by FSP. * Beware, the function will produce different results than the old FTSO contract. */ function getEpochId(uint256 _timestamp) external view returns (uint256) { } /** * Will return the current random correctly */ function getRandom(uint256 _votingRoundId) external view returns (uint256 _randomNumber) { } /** * @dev Deprecated - reverts */ function getEpochPrice(uint256) external pure returns (uint256) { revert("not supported"); } /** * Will return current price epoch data as defined by FSP */ function getPriceEpochData() external view returns ( uint256 _epochId, uint256 _epochSubmitEndTime, uint256 _epochRevealEndTime, uint256 _votePowerBlock, bool _fallbackMode ) { } /** * Will return the price epoch configuration as defined by FSP */ function getPriceEpochConfiguration() external view returns ( uint256 _firstEpochStartTs, uint256 _submitPeriodSeconds, uint256 _revealPeriodSeconds ) { } /** * @dev Deprecated - reverts */ function getEpochPriceForVoter(uint256, address) external pure returns (uint256) { revert("not supported"); } /** * Will return the current price correctly */ function getCurrentPrice() external view returns (uint256, uint256) { } /** * Will return the current price with decimals correctly */ function getCurrentPriceWithDecimals() external view returns ( uint256 _value, uint256 _timestamp, uint256 _decimals ) { } /** * Will return the current price with details correctly */ function getCurrentPriceDetails() external view returns ( uint256, uint256, PriceFinalizationType, uint256, PriceFinalizationType ) { } /** * @dev Deprecated - reverts */ function getCurrentPriceFromTrustedProviders() external pure returns (uint256, uint256) { revert("not supported"); } /** * @dev Deprecated - reverts */ function getCurrentPriceWithDecimalsFromTrustedProviders() external pure returns (uint256, uint256, uint256) { revert("not supported"); } /** * Will return the current random correctly */ function getCurrentRandom() external view returns (uint256 _currentRandom) { } ```
### FtsoRewardManagerProxy The `FtsoRewardManagerProxy` contract is designed to maintain backward compatibility with the old `FTSORewardManager` contract. It will be deployed at a different address than the original contract. This proxy only supports the most basic reward claim types and requires that reward proofs be provided in advance.
Methods in `FtsoRewardManagerProxy` ```solidity title="FtsoRewardManagerProxy.sol" /** * @dev Claims rewards correctly for delegation fees, assuming the proofs were already provided. */ function claimReward( address payable _recipient, uint256[] calldata _rewardEpochs ) external returns (uint256 _rewardAmount) { } /** * @dev Claims rewards correctly for delegation fees, assuming the proofs were already provided. */ function claim( address _rewardOwner, address payable _recipient, uint256 _rewardEpoch, bool _wrap ) external returns (uint256 _rewardAmount) { } /** * @dev Returns the current fee percentage for the data provider. */ function getDataProviderCurrentFeePercentage(address _dataProvider) external view returns (uint256 _feePercentageBIPS) { } /** * @dev Returns the fee percentage for the data provider for the given reward epoch. */ function getDataProviderFeePercentage( address _dataProvider, uint256 _rewardEpoch ) external view returns (uint256 _feePercentageBIPS) { } /** * @dev Returns the fee percentage changes for the data provider. */ function getDataProviderScheduledFeePercentageChanges(address _dataProvider) external view returns ( uint256[] memory _feePercentageBIPS, uint256[] memory _validFromEpoch, bool[] memory _fixed ) { } /** * @dev Returns the epoch reward correctly */ function getEpochReward(uint256 _rewardEpoch) external view returns (uint256 _totalReward, uint256 _claimedReward) { } /** * @dev Returns the reward state correctly */ function getStateOfRewards( address _beneficiary, uint256 _rewardEpoch ) external view returns ( address[] memory _dataProviders, uint256[] memory _rewardAmounts, bool[] memory _claimed, bool _claimable ) { } /** * @dev Returns the epochs with claimable rewards correctly */ function getEpochsWithClaimableRewards() external view returns (uint256 _startEpochId, uint256 _endEpochId) { } /** * @dev Returns the next claimable reward epoch correctly */ function nextClaimableRewardEpoch(address _rewardOwner) external view returns (uint256) { } /** * @dev Returns the epochs with unclaimed rewards correctly */ function getEpochsWithUnclaimedRewards(address _beneficiary) external view returns (uint256[] memory _epochIds) { } /** * @dev Returns the claimed rewardr correctly */ function getClaimedReward( uint256 _rewardEpoch, address _dataProvider, address _claimer ) external view returns ( bool _claimed, uint256 _amount ) { } /** * @dev Returns the reward epoch to expire next correctly */ function getRewardEpochToExpireNext() external view returns (uint256) { } /** * @dev Returns the reward epoch vote power block correctly */ function getRewardEpochVotePowerBlock(uint256 _rewardEpoch) external view returns (uint256) { } /** * @inheritdoc IFtsoRewardManager */ function getCurrentRewardEpoch() external view returns (uint256) { return rewardManager.getCurrentRewardEpochId(); } /** * @inheritdoc IFtsoRewardManager */ function getInitialRewardEpoch() external view returns (uint256 _initialRewardEpoch) { return rewardManager.getInitialRewardEpochId(); } /** * @inheritdoc IFtsoRewardManager * @dev Deprecated */ function claimRewardFromDataProviders( address payable, uint256[] calldata, address[] calldata ) external pure returns (uint256) { // return 0 } /** * @inheritdoc IFtsoRewardManager * @dev Deprecated */ function claimFromDataProviders( address, address payable, uint256[] calldata, address[] calldata, bool ) external pure returns (uint256) { // return 0 } /** * @inheritdoc IFtsoRewardManager * @dev Deprecated - reverts */ function autoClaim(address[] calldata, uint256) external pure { revert("not supported, use RewardManager"); } /** * @inheritdoc IFtsoRewardManager * @dev Deprecated - reverts */ function setDataProviderFeePercentage(uint256) external pure returns (uint256) { revert("not supported, use WNatDelegationFee"); } /** * @dev Deprecated - returns empty array, empty array, false */ function getStateOfRewardsFromDataProviders( address, uint256, address[] calldata ) external pure returns ( uint256[] memory, bool[] memory, bool ) { } /** * Deprecated - returns 0, 0 */ function getDataProviderPerformanceInfo( uint256, address ) external pure returns ( uint256, uint256 ) { } ```
## Usable existing interfaces | **Interface** | **Description** | | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`FlareContractRegistry`](/network/solidity-reference/IFlareContractRegistry) | Provides access to the addresses of all essential contracts within the Flare ecosystem. It is deployed at a fixed address across all networks, with updates to addresses managed through governance. This registry is the recommended entry point for contract interactions on all Flare networks. For easier use, consider utilizing the `ContractLibrary` from the [flare-periphery-contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts?activeTab=readme) package, which wraps the IFlareContractRegistry and simplifies interactions with Flare's smart contracts. | | [`FeeCalculator`](/ftso/solidity-reference/IFeeCalculator) | Onchain prices are now subject to potential fees, though initial fees are set to `0`. The `FeeCalculator` contract is designed to be flexible, allowing for the introduction of fees in future use cases. It calculates fees for accessing data using the `calculateFeeByIds` and `calculateFeeByIndices` methods. To stay prepared for future updates to the fee structure, it is advisable to use the FeeCalculator contract in relevant applications. | ## Example migration contract {FtsoV2MigrationExample} --- ## Migrate an app to FTSO 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 are shipped in both starter kits — [Hardhat](https://github.com/flare-foundation/flare-hardhat-starter/tree/master/contracts/adapters) and [Foundry](https://github.com/flare-foundation/flare-foundry-starter/tree/main/src/adapters). The adapter examples are Solidity _libraries_ linked at compile time, not singleton contracts deployed by Flare — your contract pulls in the relevant library and inherits the corresponding oracle's interface. ## 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` {ChainlinkExample} ### `chainlinkExample.ts` {chainlinkExample} ## 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` {PythExample} ### `pythExample.ts` {pythExample} ## FtsoApi3Adapter The `FtsoApi3Adapter` implements the `IApi3ReaderProxy` interface. The example is a `PriceGuesser` prediction market that uses the FTSO price to settle bets. ### `Api3Example.sol` {Api3Example} ### `api3Example.ts` {api3Example} ## 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` {BandExample} ### `bandExample.ts` {bandExample} ## 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` {ChronicleExample} ### `chronicleExample.ts` {chronicleExample} --- ## Build your first FTSOv2 app This guide is for developers who want to build an FTSOv2 application using either [Foundry](https://getfoundry.sh/) or [Hardhat](https://hardhat.org). In this guide, you will learn how to: - Create a contract to read the price of FLR/USD from FTSOv2 using [flare-periphery-contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts). - Compile your contract using Foundry [forge](https://getfoundry.sh/). - Deploy your contract to Flare Testnet Coston2, and interact with it using Foundry [cast](https://getfoundry.sh/). In this guide, you will learn how to: - Create a contract to read the price of FLR/USD from FTSOv2 using [flare-periphery-contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts). - Compile your contract using [Hardhat](https://hardhat.org) and run tests. - Deploy your contract to Flare Testnet Coston2. ## Prerequisites Ensure you have the following tools installed: - [Foundry](https://getfoundry.sh/) - [Node.js](https://nodejs.org/en/download/) Ensure you have the following tools installed: - [npm](https://nodejs.org/en/learn/getting-started/an-introduction-to-the-npm-package-manager) or [yarn](https://yarnpkg.com) - [Node.js](https://nodejs.org/en/download/) ## Clone the template 1. Clone the [flare-foundry-starter](https://github.com/flare-foundation/flare-foundry-starter) and navigate into the project directory: ```bash git clone https://github.com/flare-foundation/flare-foundry-starter.git cd flare-foundry-starter ``` 2. Install the project dependencies: ```bash make install ``` The `make install` target runs `forge soldeer install` and additionally fetches any npm-only dependencies (`ftso-adapters`, `pyth-sdk-solidity`, `lz-address-book`) that the starter's adapters/OFT examples need. 3. The starter ships with `remappings.txt` ready for the current periphery release. If you're adding the starter's setup to an existing project, copy these remappings: ```plaintext title="remappings.txt" @openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.2.0-rc.1/ flare-periphery/=dependencies/flare-periphery-0.1.37/ @flarenetwork/flare-periphery-contracts/=dependencies/flare-periphery-0.1.37/src/ forge-std/=dependencies/forge-std-1.9.5/src/ surl/=dependencies/surl-0.0.0/src/ surl/=dependencies/surl-0.0.0/ lz-address-book/=dependencies/lz-address-book/src/ @layerzerolabs/lz-evm-protocol-v2/=dependencies/lz-address-book/lib/LayerZero-v2/packages/layerzero-v2/evm/protocol/ @layerzerolabs/lz-evm-messagelib-v2/=dependencies/lz-address-book/lib/LayerZero-v2/packages/layerzero-v2/evm/messagelib/ @layerzerolabs/oft-evm/=dependencies/lz-address-book/lib/devtools/packages/oft-evm/ @layerzerolabs/oapp-evm/=dependencies/lz-address-book/lib/devtools/packages/oapp-evm/ solidity-bytes-utils/=dependencies/lz-address-book/lib/solidity-bytes-utils/ solady/=dependencies/solady-0.1.26/ ftso-adapters/=dependencies/ftso-adapters-0.0.1/ @pythnetwork/pyth-sdk-solidity/=dependencies/pyth-sdk-solidity-2.2.0/ ``` 1. Clone the [flare-hardhat-starter](https://github.com/flare-foundation/flare-hardhat-starter) and install dependencies: ```bash git clone https://github.com/flare-foundation/flare-hardhat-starter.git cd flare-hardhat-starter ``` 2. Install the project dependencies: ```bash yarn install ``` 3. Copy the environment file and set your private key: ```bash cp .env.example .env ``` :::danger - 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 1. Create a contract file `src/FtsoV2FeedConsumer.sol`, and add the following code to it: {FtsoV2FeedConsumerFoundry} 2. Set EVM version to `cancun` in `foundry.toml`: ```toml title="foundry.toml" [profile.default] ... evm_version = "cancun" ``` 3. To ensure everything is set up correctly, compile the contract by running: ```bash forge build ``` The output should indicate that the compilation was successful. ```plaintext [⠊] Compiling... [⠃] Compiling 27 files with Solc 0.8.27 [⠊] Solc 0.8.27 finished in 853.78ms Compiler run successful! ``` 1. Create a contract file `contracts/FTSOV2Consumer.sol`: {FtsoV2ConsumerHardhat} 2. Set EVM version to `cancun` in `hardhat.config.ts`: ```typescript module.exports = { solidity: { version: "0.8.27", settings: { evmVersion: "cancun", optimizer: { enabled: true, runs: 200, }, }, }, }; ``` 3. Compile the contract: ```bash yarn hardhat compile ``` ## Write tests 1. Create a test file `test/FtsoV2FeedConsumer_foundry.t.sol`, and add the following code: {FtsoV2FeedConsumerTestFoundry} 2. Run the tests: ```bash forge test ``` You should see a successful test result: ```plaintext [⠊] 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:FtsoV2FeedConsumerTest [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) ``` 1. Create a test directory and file `test/FtsoV2Consumer.test.ts`: {FtsoV2ConsumerTestHardhat} 2. Run the tests: ```bash yarn hardhat test ``` ## Deploy and interact with contract 1. Generate a new wallet using the [cast](https://getfoundry.sh/): ```bash cast wallet new ``` The output will look something like: ```plaintext Successfully created new keypair. Address: 0x3f6BdD26f2AE4e77AcDfA1FA24B2774ed93984B4 Private key: 0x84cf77b009a92777f75b49864e4166ddcaf8f3f5f119a19b226ab362a0cf7bf5 ``` 2. 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. ::: ```bash export ACCOUNT=
export ACCOUNT_PRIVATE_KEY= export RPC_URL="https://coston2-api.flare.network/ext/C/rpc" ``` 3. Use the [Coston2 Faucet](https://faucet.flare.network/coston2) to get some testnet C2FLR tokens. You can verify that the 100 C2FLR has arrived in your wallet: ```bash cast balance $ACCOUNT -r $RPC_URL -e ``` 4. The final step before deploying is to set the constructor arguments with the address of [`FtsoV2`](/ftso/solidity-reference) and [`FeeCalculator`](/ftso/solidity-reference) on Flare Testnet Coston2 and the [feed ID](/ftso/feeds) of FLR/USD: ```bash # see https://dev.flare.network/ftso/solidity-reference export FTSOV2_COSTON2=0x3d893C53D9e8056135C26C8c638B76C8b60Df726 export FEECALCULATOR_COSTON2=0x88A9315f96c9b5518BBeC58dC6a914e13fAb13e2 # see https://dev.flare.network/ftso/feeds export FLRUSD_FEED_ID=0x01464c522f55534400000000000000000000000000 ``` You can now deploy the contract: ```bash 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: ```plaintext [⠊] 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: 0x38604a643695959dd9fa5547d95610fb0b7393c7e8358079f47ed4bdb53c9a8f ``` ```bash export DEPLOYMENT_ADDRESS= ``` 5. Use `cast` to interact with the contract, note that this command uses the environment variables defined in the sections above.: ```bash cast send \ --private-key $ACCOUNT_PRIVATE_KEY \ --rpc-url $RPC_URL -j \ --value 0 $DEPLOYMENT_ADDRESS "getFlrUsdPrice()" ```
Expected output of the command above. ```json { "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](https://coston2-explorer.flare.network) by searching for its `transactionHash`. Congratulations! You've built your first FTSOv2 app using Foundry. 1. Create a deployment script `scripts/deployFTSOConsumer.ts`: {FtsoV2ConsumerHardhatDeploy} 2. Deploy to Flare Testnet Coston2: ```bash yarn hardhat run scripts/deployFTSOConsumer.ts --network coston2 ``` 3. Interact with the contract: Copy and paste the deployed contract address into the [Coston2 explorer](https://coston2-explorer.flare.network) to view and interact with the contract. Congratulations! You've built your first FTSOv2 app using Hardhat. :::tip[What's next] Learn how to [read feeds offchain](read-feeds-offchain) using JavaScript, Python, Rust and Go, or learn how to [change quote feed](change-quote-feed) with an onchain Solidity contract. ::: --- ## Change quote feed This guide will show you how to fetch the latest feed values for two feeds and convert them to a new quote feed while retaining the number of decimals. For example, if you need the price of `BTC/ETH`, you can fetch the latest feed values for `BTC/USD` and `ETH/USD` and calculate the price of `BTC/ETH = (BTC/USD) / (ETH/USD)`. {FtsoV2ChangeQuoteFeed} {/* prettier-ignore */} Open in Remix :::warning[Don't use test interfaces in production] The `TestFtsoV2Interface` is for **testing only**, with all methods as `view` to allow rapid development without gas costs or state changes. For production, use [`FtsoV2Interface`](/ftso/solidity-reference/FtsoV2Interface), which includes `payable` methods required for real transactions and state modifications. Make the following changes to `FtsoV2ChangeQuoteFeed.sol`: ```solidity contract FtsoV2ChangeQuoteFeed { FtsoV2Interface internal ftsoV2; constructor() { ftsoV2 = ContractRegistry.getFtsoV2(); } //... } ``` ::: :::warning[Set EVM Version to Cancun] - **Using Remix:** Set EVM version to `cancun` in the **Advanced Configurations** section of the **Solidity Compiler** tab: {" "} - **Using Hardhat or Foundry:** Set EVM version to `cancun` in [hardhat.config.ts](https://github.com/flare-foundation/flare-hardhat-starter/blob/master/hardhat.config.ts#L34) or [foundry.toml](https://github.com/flare-foundation/flare-foundry-starter/blob/master/foundry.toml). - **Using Standard Solidity JSON:** Set `evmVersion` to `cancun`: ```json { "settings": { "optimizer": { /* ... */ }, "evmVersion": "cancun" } } ``` - **Using `solc` CLI:** Set `--evm-version` to `cancun`: ```bash solc --evm-version cancun ``` ::: --- ## Create a Custom Feed 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. 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. 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. :::warning[Risk Profile] Each Custom Feed has a unique risk profile determined by its smart contract and data source, which users and developers must assess individually. ::: 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. ## Prerequisites Before you begin, ensure you have: - A conceptual understanding of smart contracts and Solidity. - Familiarity with TypeScript/JavaScript and Node.js. - Hardhat development environment set up. - Knowledge of the FTSO and an overview of the Flare Data Connector (FDC). - Environment variables set up for `VERIFIER_URL_TESTNET`, `VERIFIER_API_KEY_TESTNET`, and `COSTON2_DA_LAYER_URL` as used in the `PriceVerification.ts` script. ## Concepts ### `IICustomFeed` Interface To ensure compatibility with the FTSO system, any custom feed contract must implement the `IICustomFeed` interface. 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. #### Key functions - `feedId() external view returns (bytes21 _feedId)`:Returns the feed's unique identifier. The first byte must be 0x21 to signify a custom feed. - `read() public view returns (uint256 value)`: Returns the latest value of the feed. - `decimals() external view returns (int8)`: Returns the number of decimals for the feed's value. - `calculateFee() external pure returns (uint256 _fee)`: Calculates the fee for reading the feed. This can be zero. - `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. ### Onchain contract: `PriceVerifierCustomFeed.sol` 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/web2-json) FDC attestation type. It then exposes this price through the `IICustomFeed` interface. #### Key components - **State Variables**: The contract stores its configuration and the latest verified price. ```solidity // --- State Variables --- bytes21 public immutable feedIdentifier; // Unique ID for this custom feed. ID, e.g., 0x21 + keccak256("BTC/USD-HIST") string public expectedSymbol; // Asset symbol this feed tracks (e.g., "BTC"). int8 public decimals_; // Precision for the price. uint256 public latestVerifiedPrice; // Stores the most recent verified price. address public owner; // Address that deployed the contract, with privileges to update mappings. ``` - **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"). ```solidity constructor( bytes21 _feedId, string memory _expectedSymbol, int8 _decimals ) { // ... validation logic ... owner = msg.sender; feedIdentifier = _feedId; expectedSymbol = _expectedSymbol; decimals_ = _decimals; // ... } ``` - **`verifyPrice(IWeb2Json.Proof calldata _proof)`**: This is the heart of the contract. It accepts a proof from the FDC and performs a series of checks before updating the `latestVerifiedPrice`. 1. **Parses the API URL** from the proof to ensure the data is from the expected source. 2. **Verifies the proof's authenticity** by calling the FDC's onchain `verifyWeb2Json` function. 3. **Decodes the price data** from the proof's response body. 4. **Stores the new price** in the `latestVerifiedPrice` state variable. 5. **Emits an event** to log the update. ```solidity function verifyPrice(IWeb2Json.Proof calldata _proof) external { require(ContractRegistry.getFdcVerification().verifyWeb2Json(_proof), "FDC: Invalid Web2Json proof"); PriceData memory newPriceData = abi.decode(_proof.data.responseBody.abiEncodedData, (PriceData)); latestVerifiedPrice = newPriceData.price; emit PriceVerified(expectedSymbol, newPriceData.price, _proof.data.requestBody.url); } ``` - **`IICustomFeed` Implementation**: The contract provides concrete implementations for the interface methods. For example, `read()` and `getCurrentFeed()` simply return the latestVerifiedPrice and other stored data.
View full PriceVerifierCustomFeed.sol contract {PriceVerifierCustomFeedSol}
### Offchain script: `PriceVerification.ts` The `PriceVerification.ts` script automates fetching data from CoinGecko via the FDC and submitting it to your `PriceVerifierCustomFeed` contract. It follows these sequential steps: 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"). 2. **Prepare Attestation Request:** It constructs a request for the FDC [Web2Json](/fdc/attestation-types/web2-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. 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. 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. 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.
View full PriceVerification.ts script. {PriceVerificationTs}
## Deploy and use a Custom feed 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. ### 1. Clone the hardhat starter First, clone the [`flare-hardhat-starter`](https://github.com/flare-foundation/flare-hardhat-starter) repository and navigate into the project directory: ```bash git clone https://github.com/flare-foundation/flare-hardhat-starter.git cd flare-hardhat-starter ``` ### 2. Install dependencies Install the project dependencies using `npm` or `yarn`: ```bash npm install # or yarn install ``` ### 3. Set up environment variables Copy the example environment file and update it with your own credentials. ```bash cp .env.example .env ``` You will need to provide the following: - `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). - `VERIFIER_URL_TESTNET`: The URL for the Web2Json Verifier service. You can leave the default value. - `VERIFIER_API_KEY_TESTNET`: An API key for the verifier service. You can get one from the [Flare Developer Portal](https://portal.flare.network/). - `COSTON2_DA_LAYER_URL`: The URL for the Data Availability Layer on Coston2. You can leave the default value. :::danger Never commit your `.env` file or share your private keys publicly. ::: ### 4. Run the verification script The `PriceVerification.ts` script, located in `scripts/customFeeds/`, automates the entire process. Execute it on the Coston2 Testnet: ```bash yarn hardhat run scripts/customFeeds/PriceVerification.ts --network coston2 ``` The script will: 1. Deploy the `PriceVerifierCustomFeed.sol` contract. 2. Prepare an attestation request for the CoinGecko API. 3. Submit the request to the Flare Data Connector (FDC). 4. Wait for the attestation to be finalized. 5. Retrieve the proof from the Data Availability layer. 6. Submit the proof to the deployed `PriceVerifierCustomFeed` contract. ### 5. Understanding the output The script will log its progress. A successful run will display the deployed contract address and the final verified price: ```text Deploying PriceVerifierCustomFeed... PriceVerifierCustomFeed deployed to: 0x... (contract address) Preparing data... // ... (attestation request details) Submitting attestation requests... // ... (transaction details) Waiting for round 12345 to be finalized... Round finalized. Retrieving proofs... // ... (proof details) Submitting proof to custom feed... Proof for BTCPrice submitted successfully... Latest verified price: 12345 Price verification process completed successfully. ``` ### 6. Verify on explorer You can view your deployed contract on the [Coston2 Explorer](https://coston2-explorer.flare.network/). 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. ## Propose a new Custom Feed 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. 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. The request should include a business justification and a link to the verified contract on a block explorer. The Flare Foundation will review the submission for eligibility. {/* prettier-ignore */} Propose Custom Feed ## Conclusion You now have the tools to create FTSO Custom Feeds, bringing diverse, verifiable data from any API onto the Flare network. This capability greatly expands the possibilities for DeFi and other onchain applications. Remember, the security of your Custom Feed depends entirely on its smart contract logic and data verification process. Prioritize careful design and rigorous testing in your implementations. --- ## Make a volatility incentive :::info Before reading this guide, make sure you understand [FTSOv2's Volatility Incentive Mechanism](/ftso/overview#volatility-incentive-mechanism). ::: This guide provides code examples demonstrating how to make an FTSOv2 volatility incentive offer using various programming languages. To make a volatility incentive offer, you need three key pieces of information: 1. **RPC Endpoint URL:** The RPC Endpoint URL determines which network your code will interact with. You can use a node provider service or point to your [own RPC node](/run-node#rpc-node). A comprehensive list of public and private RPC endpoints for all Flare networks is available on the [Network Configuration](/network/overview#configuration) page. 2. **Contract Address:** The address for the `FastUpdateIncentiveManager` contract varies by network. You can obtain this address in two ways: - **From the Solidity Reference page:** Find the `FastUpdateIncentiveManager` address for each network on the [Solidity Reference](/ftso/solidity-reference) page. **OR** - **Query the FlareContractRegistry Contract:** The `FlareContractRegistry` contract has the same address across all networks. You can query it to get the `FastUpdateIncentiveManager` contract address. Refer to the specific language guides for examples: - [JavaScript](/network/guides/flare-for-javascript-developers#make-query) - [Python](/network/guides/flare-for-python-developers#make-query) - [Rust](/network/guides/flare-for-rust-developers#make-query) - [Go](/network/guides/flare-for-go-developers#make-query) 3. **Cost of Increasing the Sample Size:** FTSOv2 allows you to increase the sample size, i.e., the expected number of providers who can submit a block-latency feed update. The cost for this increases dynamically with the expected sample size. A single volatility incentive lasts for a period of 8 blocks. :::tip All examples in this guide are available at [developer-hub/examples](https://github.com/flare-foundation/developer-hub/tree/main/examples). ::: This example uses [web3.js](https://github.com/web3/web3.js) to make an FTSOv2 volatility incentive offer on Flare Testnet Coston2. {VolIncentiveWeb3Js} This example uses [ethers.js](https://github.com/ethers-io/ethers.js/) to make an FTSOv2 volatility incentive offer on Flare Testnet Coston2. {VolIncentiveEthersJs} This example uses [web3.py](https://github.com/ethereum/web3.py) to make an FTSOv2 volatility incentive offer on Flare Testnet Coston2. {VolIncentiveWeb3Py} This example uses [alloy-rs](https://github.com/ethereum/web3.py) to make an FTSOv2 volatility incentive offer on Flare Testnet Coston2. ```bash cargo add alloy eyre tokio --features alloy/full,tokio/rt,tokio/rt-multi-thread,tokio/macros ``` {VolIncentiveRust} This example uses the Go API from [Geth](https://geth.ethereum.org) to make an FTSOv2 volatility incentive offer on Flare Testnet Coston2. ```bash go get github.com/ethereum/go-ethereum/ethclient go get github.com/ethereum/go-ethereum/accounts ``` The project structure should look like: ```plaintext developer-hub-go/ ├── coston2/ │ └── *.go ├── flare/ │ └── *.go ├── main.go ├── go.mod └── go.sum ``` With Go, you need to manually fetch the contract's ABI and generate the Go bindings. Copy the [FastUpdatesIncentiveManager ABI](https://api.routescan.io/v2/network/testnet/evm/114/etherscan/api?module=contract&action=getabi&address=0x003e9bD18f73e0B25BED0DC8382Bde6aa999525c&format=raw) and paste it into a file named `FastUpdatesIncentiveManager.abi`, located in the root of your project, i.e. same level as `go.mod`. Then using [abigen](https://geth.ethereum.org/docs/tools/abigen), generate the Go bindings. ```bash abigen --abi FastUpdatesIncentiveManager.abi --pkg coston2 --type FastUpdatesIncentiveManager --out coston2/FastUpdatesIncentiveManager.go ``` {VolIncentiveGo} --- ## Query feed configuration This guide provides code examples demonstrating how to read FTSOv2 feed configurations offchain using various programming languages. To achieve this, you need two key pieces of information: 1. **RPC Endpoint URL:** The RPC Endpoint URL determines which network your code will interact with. You can either use a node provider service or point to your [own RPC node](/run-node#rpc-node). A list of public and private RPC endpoints for all Flare networks is available on the [Network Configuration](/network/overview#configuration) page. 2. **Contract Address:** The address for the `FastUpdatesConfiguration` contract varies by network. You can obtain this address in two ways: - **From the Solidity Reference page:** Find the `FastUpdatesConfiguration` address for each network on the [Solidity Reference](/ftso/solidity-reference) page. **OR** - **Query the FlareContractRegistry Contract:** The `FlareContractRegistry` contract has the same address across all networks. You can query it to get the `FastUpdatesConfiguration` contract address. Refer to the specific language guides for examples: - [JavaScript](/network/guides/flare-for-javascript-developers#make-query) - [Python](/network/guides/flare-for-python-developers#make-query) - [Rust](/network/guides/flare-for-rust-developers#make-query) - [Go](/network/guides/flare-for-go-developers#make-query) :::tip All examples in this guide are available at [developer-hub/examples](https://github.com/flare-foundation/developer-hub/tree/main/examples). ::: This example uses [web3.js](https://github.com/web3/web3.js) and [Flare Periphery Contract Artifacts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contract-artifacts) to retrieve FTSOv2 feed configurations on Flare Testnet Coston2. ```bash npm install web3 npm install @flarenetwork/flare-periphery-contract-artifacts ``` {FtsoConfigWeb3Js} This example uses [web3.js](https://github.com/web3/web3.js) and [Flare Periphery Contract Artifacts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contract-artifacts) to retrieve FTSOv2 feed configurations on Flare Testnet Coston2. ```bash npm install ethers npm install @flarenetwork/flare-periphery-contract-artifacts ``` {FtsoConfigEthersJs} This example uses [web3.py](https://github.com/ethereum/web3.py) to retrieve FTSOv2 feed configurations on Flare Testnet Coston2. ```bash uv add web3 ``` ```bash pip install web3 ``` {FtsoConfigWeb3Py} This example uses [alloy-rs](https://github.com/alloy-rs) to retrieve FTSOv2 feed configurations on Flare Testnet Coston2. ```bash cargo add alloy eyre tokio --features alloy/full,tokio/rt,tokio/rt-multi-thread,tokio/macros ``` {FtsoConfigRust} This example uses the Go API from [Geth](https://geth.ethereum.org) to retrieve FTSOv2 feed configurations on Flare Testnet Coston2. ```bash go get github.com/ethereum/go-ethereum/ethclient ``` With Go, you need to manually fetch the contract's ABI and generate the Go bindings. Copy the [FastUpdatesConfiguration ABI](https://api.routescan.io/v2/network/testnet/evm/114/etherscan/api?module=contract&action=getabi&address=0xE7d1D5D58cAE01a82b84989A931999Cb34A86B14&format=raw) and paste it into a file named `FastUpdatesConfiguration.abi`, located in the root of your project, i.e. same level as `go.mod`. Then using [abigen](https://geth.ethereum.org/docs/tools/abigen), generate the Go bindings. ```bash abigen --abi FastUpdatesConfiguration.abi --pkg main --type FastUpdatesConfiguration --out FastUpdatesConfiguration.go ``` {FtsoConfigGo} --- ## Read feeds offchain This guide provides code examples demonstrating how to read FTSOv2 feeds offchain using various programming languages. To read a block-latency feed offchain, you need three key pieces of information: 1. **RPC Endpoint URL:** The RPC Endpoint URL determines which network your code will interact with. You can use a node provider service or point to your [own RPC node](/run-node#rpc-node). A comprehensive list of public and private RPC endpoints for all Flare networks is available on the [Network Configuration](/network/overview#configuration) page. 2. **Contract Address:** Feeds are served on the `FtsoV2` contract, whose address varies by network. You can obtain this address in two ways: - **From the Solidity Reference page:** Find the `FtsoV2` address for each network on the [Solidity Reference](/ftso/solidity-reference) page. **OR** - **Query the FlareContractRegistry Contract:** The `FlareContractRegistry` contract has the same address across all networks. You can query it to get the `FtsoV2` contract address. Refer to the specific language guides for examples: - [JavaScript](/network/guides/flare-for-javascript-developers#make-query) - [Python](/network/guides/flare-for-python-developers#make-query) - [Rust](/network/guides/flare-for-rust-developers#make-query) - [Go](/network/guides/flare-for-go-developers#make-query) 3. **Feed IDs:** The feeds you want to read are uniquely identified by their ID. A list of feed IDs is provided on the [Block-Latency Feeds](/ftso/feeds) page. :::tip All examples in this guide are available at [developer-hub/examples](https://github.com/flare-foundation/developer-hub/tree/main/examples). ::: This example uses [web3.js](https://github.com/web3/web3.js) and [Flare Periphery Contract Artifacts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contract-artifacts) to retrieve FTSOv2 feed data for FLR/USD, BTC/USD, and ETH/USD on Flare Testnet Coston2. ```bash npm install web3 npm install @flarenetwork/flare-periphery-contract-artifacts ``` {ReadOffhainWeb3Js} This example uses [web3.js](https://github.com/web3/web3.js) and [Flare Periphery Contract Artifacts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contract-artifacts) to retrieve FTSOv2 feed data for FLR/USD, BTC/USD, and ETH/USD on Flare Testnet Coston2. ```bash npm install ethers npm install @flarenetwork/flare-periphery-contract-artifacts ``` {ReadOffhainEthersJs} This example uses [web3.py](https://github.com/ethereum/web3.py) to retrieve FTSOv2 feed values for FLR/USD, BTC/USD, and ETH/USD on Flare Testnet Coston2. ```bash uv add web3 ``` ```bash pip install web3 ``` {ReadOffhainWeb3Py} This example uses [alloy-rs](https://github.com/alloy-rs) to retrieve FTSOv2 feed data for FLR/USD, BTC/USD, and ETH/USD on Flare Testnet Coston2. ```bash cargo add alloy eyre tokio --features alloy/full,tokio/rt,tokio/rt-multi-thread,tokio/macros ``` {ReadOffhainRust} This example uses the Go API from [Geth](https://geth.ethereum.org) to retrieve FTSOv2 feed data for FLR/USD, BTC/USD, and ETH/USD from Flare Testnet Coston2. ```bash go get github.com/ethereum/go-ethereum/ethclient ``` With Go, you need to manually fetch the contract's ABI and generate the Go bindings. Copy the [FtsoV2 ABI](/ftso/solidity-reference) and paste it into a file named `FtsoV2.abi`, located in the root of your project, i.e. same level as `go.mod`. Then using [abigen](https://geth.ethereum.org/docs/tools/abigen), generate the Go bindings. ```bash abigen --abi FtsoV2.abi --pkg main --type FtsoV2 --out FtsoV2.go ``` {ReadOffhainGo} --- ## Scaling **Scaling** is an advanced framework designed to complement FTSOv2's block-latency feeds by providing robust commit-reveal anchored prices every 90 seconds. It operates through data providers who submit feed estimates weighted by their stake in the network. These estimates are processed using a weighted median algorithm to determine consensus feed values. Scaling offers several enhancements: - **Broad coverage:** Up to 1000 data feeds across equities, commodities, and crypto, with 2 weeks of historical data. - **Integrity:** Commit-reveal with approximately 100 independent providers every 90 seconds. - **Efficiency**: Median calculation and optimized storage uses \<5% of network bandwidth at peak usage. ## Architecture Scaling is structured into four phases: - **Commit**: Data providers compute and submit data proposals encoded in a commit hash. To maintain security, the actual feed values are not disclosed at this stage. - **Reveal**: Data providers reveal their data to one another, alongside the random numbers used to generate their commit hash. - **Sign**: Valid data reveals are used to calculate median values, which are aggregated into an efficient Merkle tree structure and published onchain. - **Finalization**: Once a sufficient weight of signatures for the same Merkle root is collected, a randomly chosen provider (or any other entity in case of a failure), can collect and submit them onchain for verification. Once the finalization phase is complete, the Merkle root is published onchain, making it available to all other smart contracts for verification of calculation results. This structured approach not only maintains data integrity and accuracy but also incentivizes active participation from data providers, contributing to the overall efficiency and reliability of Scaling. :::tip[Interested in learning more?] For a detailed explanation of the Scaling mechanism, read Section 2 of the [FTSOv2 whitepaper](https://dev.flare.network/pdf/whitepapers/20240223-FlareTimeSeriesOracleV2.pdf). ::: ### Weighted Median Calculation Scaling uses a weighted median to determine the consensus feed value from multiple data providers. This ensures that prices reflect the stake-weighted consensus, rewarding honest participants while making manipulation costly. #### How it works 1. **Collect estimates:** All valid feed submissions are gathered. 2. **Sort by value:** Estimates are ordered from lowest to highest. 3. **Assign weights:** Each submission is weighted by the provider's stake (voting power). 4. **Calculate threshold:** Compute the total weight $W$, then take half ($M = W/2$). 5. **Accumulate weights:** Move through the sorted list, adding weights until the cumulative total ≥ $M$. 6. **Select median:** The corresponding feed value is the weighted median. **In summary:** Line up all providers' values, give each one influence proportional to their stake, then find the middle point of this weighted distribution.
Step-by-step illustration. Suppose we have the following data estimates from five providers with their corresponding weights: | **Estimate (Feed Value)** | **Weight** | | ------------------------- | ---------- | | 250 | 4 | | 200 | 2 | | 100 | 1 | | 150 | 3 | | 300 | 1 | First, sort these estimates: | **Estimate (Feed Value)** | **Weight** | | ------------------------- | ---------- | | 100 | 1 | | 150 | 3 | | 200 | 2 | | 250 | 4 | | 300 | 1 | Calculate the total weight: $W = 1 + 3 + 2 + 4 + 1 = 11$. The median threshold $ M $ is $ \frac{W}{2} = 5.5 $. Accumulate the weights: - For 100: Cumulative weight = 1 - For 150: Cumulative weight = 1 + 3 = 4 - For 200: Cumulative weight = 4 + 2 = 6 At this point, the cumulative weight (6) exceeds the median threshold (5.5). Here, the weighted median is 200, which best represents the majority stake-weighted consensus.
This weighted median calculation ensures that the consensus feed value reflects the most influential estimates, balancing the data based on the providers' voting power. This method is designed to be robust against outliers and manipulation, ensuring a fair and reliable consensus process. ### Incentivization Mechanism Scaling's incentive model is designed to keep data providers active, accurate, and honest, while ensuring the long-term integrity of the feeds. Rewards and penalties are applied each voting epoch, balancing positive incentives with strict accountability. #### Rewards The total reward pool for each epoch is divided into three parts: - **Median closeness rewards:** Granted to providers whose submissions fall within the interquartile range (IQR) band of the finalized value. - If a submission lies exactly on the IQR boundary, a pseudo-random process determines inclusion. - Additional governance-defined reward bands (fixed % around the finalized value) further refine fair allocation. - **Signature rewards:** Earned for correctly signing and contributing to the Merkle tree of revealed values. - **Finalization rewards:** Paid to the provider (or fallback entity) that successfully submits the finalized Merkle root onchain. #### Penalties To discourage manipulation and negligence: - **Non-matching or missing reveals:** Reduced or forfeited rewards. - **Consistently invalid submissions:** Negative cumulative rewards, making them non-claimable. - R**andomness omissions:** Penalties for failing to supply valid random numbers during commit-reveal, preventing gaming of the system. #### Additional incentives - **Inflation reward offers:** Automatically triggered for certain feeds to guarantee coverage. - **Community reward offers:** Anyone can sponsor extra rewards for specific feeds before a reward epoch. :::info[Why this matters] This layered reward and penalty system makes accurate reporting economically attractive and misbehavior costly. As a result, Scaling maintains robust participation across both common and niche feeds, strengthening the reliability of Flare's oracle infrastructure. ::: ## Watch the video --- ## Getting Started(Scaling) 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: 1. **Fetch anchor feed data offchain:** Use the Data Availability (DA) Layer API to retrieve anchor feeds and their associated cryptographic proofs. 2. **Verify the proof onchain:** Validate the provided proof onchain to ensure the data matches the finalized version committed by [Scaling](/ftso/scaling/overview). 3. **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 :::warning[Rate limits] The public DA Layer endpoints are rate-limited. To request an API key for higher limits, create an [API Key Request Issue](https://github.com/flare-foundation/developer-hub/issues/new/choose). ::: 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/` | | Flare Testnet Coston2 | `https://ctn2-data-availability.flare.network/` | | Songbird Testnet Coston | `https://ctn-data-availability.flare.network/` | All networks have the same API structure. For a full list of endpoints see [Data Availability API Reference](/ftso/scaling/solidity-reference/data-availability-api). ## 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: ```bash 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" ] }' ``` {FetchAnchorFeedsJs} {FetchAnchorFeedsPy} {FetchAnchorFeedsGo} {FetchAnchorFeedsRs} #### 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](https://flare-systems-explorer.flare.network/voting-round)). - `id`: The feed ID (refer to the [list of anchor feeds](/ftso/scaling/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) ```json [ { "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: ```bash curl -X 'GET' \ 'https://flr-data-availability.flare.network/api/v0/fsp/status' \ -H 'accept: application/json' ``` :::warning - 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 ```json { "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](https://flare-systems-explorer.flare.network/voting-round/839641), which **started at** `1733997690`. - The **most recently finalized FTSO voting round** has `voting_round_id` [839640](https://flare-systems-explorer.flare.network/voting-round/839640), which **started at** `1733997600`. ## Verifying proof onchain To verify feed data onchain, use the [`FtsoV2Interface`](/ftso/solidity-reference/FtsoV2Interface). This interface offers the [`verifyFeedData`](/ftso/solidity-reference/FtsoV2Interface#verifyfeeddata) method to validate feed data and proof against the onchain Merkle root. The function requires a single input struct [`FeedDataWithProof`](/ftso/solidity-reference/FtsoV2Interface#feeddatawithproof), which includes the feed data and voting round ID within the [`FeedData`](/ftso/solidity-reference/FtsoV2Interface#feeddata) struct, and a Merkle proof. An example contract verifying and consuming anchor feeds onchain {FtsoV2AnchorFeedConsumer} {/* prettier-ignore */} Open in Remix ## 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: {FetchAndVerifyAnchorOnchainJs} {FetchAndVerifyAnchorOnchainPy} {FetchAndVerifyAnchorOnchainGo} {FetchAndVerifyAnchorOnchainRs} --- ## Anchor Feeds Scaling's anchor feeds update every 90 seconds with each new voting epoch on Flare. These feeds, accessible offchain, support various asset classes including equities, commodities, and cryptocurrencies.
How Feed IDs are derived. Each block-latency feed is uniquely identified by an ID composed of three components in a structured encoding process: 1. **Category:** Indicates the type of asset - Crypto: `01`, Forex: `02`, Commodity: `03`, Stock: `04`, Custom Feeds: `21` 2. **Hex-Encoded Feed Name:** The name of the feed is converted to a hexadecimal format. 3. **Zero Padding:** The combined category and hex-encoded feed name are padded with zeros to a length of 21 bytes. 4. **0x-prefix:** The resulting string is then prefixed with `0x`. As an example, take the feed name `FLR/USD`: - **Category**: `01` (Crypto) - **Hex-Encoded Feed Name**: `464c522f555344` (hexadecimal representation of `FLR/USD`) - **Zero Padding**: The category and hex-encoded feed name combined and padded: `01464c522f55534400000000000000000000000000` - **Final ID**: Adding the `0x` prefix results in `0x01464c522f55534400000000000000000000000000` {GetFeedIdSol} {GetFeedIdJS} {GetFeedIdPy} {GetFeedIdRs} {GetFeedIdGo}
🚦 Understanding feed risks. When building applications, developers must carefully evaluate the quality of the data they use. As a developer, you are responsible for identifying and assessing the accuracy, availability, and overall quality of the data you choose to integrate. It is important to understand that all data feeds carry inherent risks. The table below categorizes feeds into three risk levels based on their market integrity, ranging from lowest to highest, a feed is assigned a higher risk level if it fails to meet **all** the criteria required for classification within a lower risk level: | **Aspect** | 🟢 **Low Risk** | 🟡 **Medium Risk** | 🔴 **High Risk** | | --------------------------- | ----------------------------------------- | ------------------------------------------- | ------------------------------------------------------ | | **Intrinsic Volatility** | Low, stable price trends | Moderate price fluctuations | High, frequent price swings | | **Liquidity Variation** | Abundant and consistent | Sufficient but variable | Limited and inconsistent | | **Liquidity Concentration** | Broad and well-distributed across venues | Somewhat concentrated | Highly concentrated in a few sources | | **Asset Spread Risk** | Tight spreads, minimal bid-ask gaps | Moderate spreads, acceptable bid-ask gaps | Wide spreads, significant bid-ask gaps | | **Cross Rate Risk** | Low correlation, direct pricing available | Moderate correlation, indirect pricing used | High correlation, dependent on multiple intermediaries | **Other risk categories:** - ⚫ **New Feed** New tokens lack historical data for proper risk assessment and stable price discovery. Users must recognize these assets carry higher volatility risks and verify feed reliability independently. After a monitoring period, feeds will be assigned risk levels or potentially discontinued. Each feed undergoes a rigorous assessment process prior to deployment. The evaluation criteria may vary depending on the specific type of feed being implemented and can evolve over time as our understanding of market integrity risks improves.
:::warning Feed IDs are not addresses. They are `bytes21` structured encodings that combine the category and feed name to ensure each feed has a unique identifier. ::: --- ## Scaling Reference ## Deployed Contracts export const contracts = [ "FtsoFeedPublisher", "FtsoInflationConfigurations", "FtsoRewardOffersManager", ]; ## Interfaces --- ## Basic integration --- ## Data Availability Api --- ## IFtsoFeedPublisher FtsoFeedPublisher interface. Sourced from `IFtsoFeedPublisher.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFtsoFeedPublisher.sol). ## Functions ### feedsHistorySize The size of the feeds history. ```solidity function feedsHistorySize( ) external view returns ( uint256 ); ``` ### ftsoProtocolId The FTSO protocol id. ```solidity function ftsoProtocolId( ) external view returns ( uint8 ); ``` ### getCurrentFeed Returns the current feed. ```solidity function getCurrentFeed( bytes21 _feedId ) external view returns ( struct IFtsoFeedPublisher.Feed ); ``` #### Parameters - `_feedId`: Feed id. ### getFeed Returns the feed for given voting round id. ```solidity function getFeed( bytes21 _feedId, uint256 _votingRoundId ) external view returns ( struct IFtsoFeedPublisher.Feed ); ``` #### Parameters - `_feedId`: Feed id. - `_votingRoundId`: Voting round id. ### publish Publishes feeds. ```solidity function publish( struct IFtsoFeedPublisher.FeedWithProof[] _proofs ) external; ``` #### Parameters - `_proofs`: The FTSO feeds with proofs to publish. ## Events ### FtsoFeedPublished Event emitted when a new feed is published. ```solidity event FtsoFeedPublished( uint32 votingRoundId, bytes21 id, int32 value, uint16 turnoutBIPS, int8 decimals ) ``` ## Structures ### Feed The FTSO feed struct. ```solidity struct Feed { uint32 votingRoundId; bytes21 id; int32 value; uint16 turnoutBIPS; int8 decimals; } ``` ### FeedWithProof The FTSO feed with proof struct. ```solidity struct FeedWithProof { bytes32[] merkleProof; struct IFtsoFeedPublisher.Feed body; } ``` ### Random The FTSO random struct. ```solidity struct Random { uint32 votingRoundId; uint256 value; bool isSecure; } ``` --- ## IFtsoInflationConfigurations FtsoInflationConfigurations interface. Sourced from `IFtsoInflationConfigurations.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFtsoInflationConfigurations.sol). ## Functions ### getFtsoConfiguration Returns the FTSO configuration at `_index`. ```solidity function getFtsoConfiguration( uint256 _index ) external view returns ( struct IFtsoInflationConfigurations.FtsoConfiguration ); ``` #### Parameters - `_index`: The index of the FTSO configuration. ### getFtsoConfigurations Returns the FTSO configurations. ```solidity function getFtsoConfigurations( ) external view returns ( struct IFtsoInflationConfigurations.FtsoConfiguration[] ); ``` ## Structures ### FtsoConfiguration The FTSO configuration struct. ```solidity struct FtsoConfiguration { bytes feedIds; uint24 inflationShare; uint16 minRewardedTurnoutBIPS; uint24 primaryBandRewardSharePPM; bytes secondaryBandWidthPPMs; uint16 mode; } ``` --- ## IFtsoRewardOffersManager FtsoRewardOffersManager interface. Sourced from `IFtsoRewardOffersManager.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFtsoRewardOffersManager.sol). ## Functions ### minimalRewardsOfferValueWei Minimal rewards offer value (in wei). ```solidity function minimalRewardsOfferValueWei( ) external view returns ( uint256 ); ``` ### offerRewards Allows community to offer rewards. ```solidity function offerRewards( uint24 _nextRewardEpochId, struct IFtsoRewardOffersManager.Offer[] _offers ) external payable; ``` #### Parameters - `_nextRewardEpochId`: The next reward epoch id. - `_offers`: The list of offers. ## Events ### InflationRewardsOffered Event emitted when inflation rewards are offered. ```solidity event InflationRewardsOffered( uint24 rewardEpochId, bytes feedIds, bytes decimals, uint256 amount, uint16 minRewardedTurnoutBIPS, uint24 primaryBandRewardSharePPM, bytes secondaryBandWidthPPMs, uint16 mode ) ``` ### MinimalRewardsOfferValueSet Event emitted when the minimal rewards offer value is set. ```solidity event MinimalRewardsOfferValueSet( uint256 valueWei ) ``` ### RewardsOffered Event emitted when a reward offer is received. ```solidity event RewardsOffered( uint24 rewardEpochId, bytes21 feedId, int8 decimals, uint256 amount, uint16 minRewardedTurnoutBIPS, uint24 primaryBandRewardSharePPM, uint24 secondaryBandWidthPPM, address claimBackAddress ) ``` ## Structures ### Offer Defines a reward offer. ```solidity struct Offer { uint120 amount; bytes21 feedId; uint16 minRewardedTurnoutBIPS; uint24 primaryBandRewardSharePPM; uint24 secondaryBandWidthPPM; address claimBackAddress; } ``` --- ## FtsoV2Interface Primary interface for interacting with FTSOv2. This is a long-term support (LTS) interface, designed to ensure continuity even as underlying contracts evolve or protocols migrate to new versions. Sourced from `FtsoV2Interface.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/LTS/FtsoV2Interface.sol). ## Functions ### getFeedById Returns stored data of a feed. A fee (calculated by the FeeCalculator contract) may need to be paid. ```solidity function getFeedById( bytes21 _feedId ) external payable returns ( uint256 _value, int8 _decimals, uint64 _timestamp ); ``` #### Parameters - `_feedId`: The id of the feed. #### Returns - `_value`: The value for the requested feed. - `_decimals`: The decimal places for the requested feed. - `_timestamp`: The timestamp of the last update.
Sample contract usage {FTSOV2FeedById}
Open sample in Remix ### getFeedByIdInWei Returns value in wei and timestamp of a feed. A fee (calculated by the FeeCalculator contract) may need to be paid. ```solidity function getFeedByIdInWei( bytes21 _feedId ) external payable returns ( uint256 _value, uint64 _timestamp ); ``` #### Parameters - `_feedId`: The id of the feed. #### Returns - `_value`: The value for the requested feed in wei (i.e. with 18 decimal places). - `_timestamp`: The timestamp of the last update.
Sample contract usage {FTSOV2FeedByIdWei}
Open sample in Remix ### getFeedsById Returns stored data of each feed. A fee (calculated by the FeeCalculator contract) may need to be paid. ```solidity function getFeedsById( bytes21[] _feedIds ) external payable returns ( uint256[] _values, int8[] _decimals, uint64 _timestamp ); ``` #### Parameters - `_feedIds`: The list of feed ids. #### Returns - `_values`: The list of values for the requested feeds. - `_decimals`: The list of decimal places for the requested feeds. - `_timestamp`: The timestamp of the last update.
Sample contract usage {FTSOV2FeedsById}
Open sample in Remix ### getFeedsByIdInWei Returns value of each feed and a timestamp. For some feeds, a fee (calculated by the FeeCalculator contract) may need to be paid. ```solidity function getFeedsByIdInWei( bytes21[] _feedIds ) external payable returns ( uint256[] _values, uint64 _timestamp ); ``` #### Parameters - `_feedIds`: Ids of the feeds. #### Returns - `_values`: The list of values for the requested feeds in wei (i.e. with 18 decimal places). - `_timestamp`: The timestamp of the last update.
Sample contract usage {FTSOV2FeedsByIdWei}
Open sample in Remix ### getFtsoProtocolId Returns the FTSO protocol id. ```solidity function getFtsoProtocolId() external view returns (uint256); ``` ### getSupportedFeedIds Returns the list of currently supported (active) feed ids. To enumerate every feed id that has ever existed, combine the result with [`getFeedIdChanges`](#getfeedidchanges). ```solidity function getSupportedFeedIds() external view returns (bytes21[] memory _feedIds); ``` #### Returns - `_feedIds`: The list of supported feed ids. ### getFeedIdChanges Returns the list of feed id changes (old/new pairs). ```solidity function getFeedIdChanges() external view returns (FeedIdChange[] memory _feedIdChanges); ``` #### Returns - `_feedIdChanges`: The list of changed feed id pairs. ### calculateFeeById Calculates the fee for fetching a single feed. ```solidity function calculateFeeById(bytes21 _feedId) external view returns (uint256 _fee); ``` #### Parameters - `_feedId`: The id of the feed. #### Returns - `_fee`: The fee for fetching the feed. ### calculateFeeByIds Calculates the fee for fetching multiple feeds at once. ```solidity function calculateFeeByIds(bytes21[] memory _feedIds) external view returns (uint256 _fee); ``` #### Parameters - `_feedIds`: The list of feed ids. #### Returns - `_fee`: The combined fee. ### verifyFeedData Checks if the feed data is valid (i.e. is part of the confirmed Merkle tree). ```solidity function verifyFeedData( struct FtsoV2Interface.FeedDataWithProof _feedData ) external view returns ( bool ); ``` #### Parameters - `_feedData`: Structure containing data about the feed (FeedData structure) and Merkle proof. #### Returns - `_0`: true if the feed data is valid.
Sample contract usage {FTSOV2AnchorConsumer}
Open sample in Remix ## Structures ### FeedData Feed data structure ```solidity struct FeedData { uint32 votingRoundId; bytes21 id; int32 value; uint16 turnoutBIPS; int8 decimals; } ``` ### FeedDataWithProof Feed data with proof structure ```solidity struct FeedDataWithProof { bytes32[] proof; struct FtsoV2Interface.FeedData body; } ``` ### FeedIdChange Pair representing a feed id rename (e.g. when a feed is relabelled). ```solidity struct FeedIdChange { bytes21 oldFeedId; bytes21 newFeedId; } ``` ## Events ### FeedIdChanged Emitted when a feed id is changed (e.g. when a feed is renamed). ```solidity event FeedIdChanged(bytes21 indexed oldFeedId, bytes21 indexed newFeedId); ``` --- ## IFastUpdateIncentiveManager Interface for making volatility incentive offers. Sourced from `IFastUpdateIncentiveManager.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFastUpdateIncentiveManager.sol). ## Functions ### getBaseScale Viewer for the base value of the scale itself. ```solidity function getBaseScale( ) external view returns ( Scale ); ``` ### getCurrentSampleSizeIncreasePrice Viewer for the current value of sample size increase price. ```solidity function getCurrentSampleSizeIncreasePrice( ) external view returns ( Fee ); ``` ### getExpectedSampleSize Viewer for the current value of the expected sample size. ```solidity function getExpectedSampleSize( ) external view returns ( SampleSize ); ``` ### getPrecision Viewer for the current value of the unit delta's precision (the fractional part of the scale). ```solidity function getPrecision( ) external view returns ( Precision ); ``` ### getRange Viewer for the current value of the per-block variation range. ```solidity function getRange( ) external view returns ( Range ); ``` ### getScale Viewer for the current value of the scale itself. ```solidity function getScale( ) external view returns ( Scale ); ``` ### offerIncentive The entry point for third parties to make incentive offers. It accepts a payment and, using the contents of `_offer`, computes how much the expected sample size will be increased to apply the requested (but capped) range increase. If the ultimate value of the range exceeds the cap, funds are returned to the sender in proportion to the amount by which the increase is adjusted to reach the cap. ```solidity function offerIncentive( struct IFastUpdateIncentiveManager.IncentiveOffer _offer ) external payable; ``` #### Parameters - `_offer`: The requested amount of per-block variation range increase, along with a cap for the ultimate range. ### rangeIncreaseLimit The maximum value that the range can be increased to by an incentive offer. ```solidity function rangeIncreaseLimit( ) external view returns ( Range ); ``` ### rangeIncreasePrice The price for increasing the per-block range of variation by 1, prorated for the actual amount of increase. ```solidity function rangeIncreasePrice( ) external view returns ( Fee ); ``` ### sampleIncreaseLimit The maximum amount by which the expected sample size can be increased by an incentive offer. This is controlled by governance and forces a minimum cost to increasing the sample size greatly, which would otherwise be an attack on the protocol. ```solidity function sampleIncreaseLimit( ) external view returns ( SampleSize ); ``` ## Events ### IncentiveOffered Event emitted when an incentive is offered. ```solidity event IncentiveOffered( uint24 rewardEpochId, Range rangeIncrease, SampleSize sampleSizeIncrease, Fee offerAmount ) ``` ### InflationRewardsOffered Event emitted when inflation rewards are offered. ```solidity event InflationRewardsOffered( uint24 rewardEpochId, struct IFastUpdatesConfiguration.FeedConfiguration[] feedConfigurations, uint256 amount ) ``` ## Structures ### IncentiveOffer Incentive offer structure. ```solidity struct IncentiveOffer { Range rangeIncrease; Range rangeLimit; } ``` --- ## IFastUpdater Interface for updating block-latency feeds. Sourced from `IFastUpdater.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFastUpdater.sol). ## Functions ### blockScoreCutoff Informational getter concerning the eligibility criterion for being chosen by sortition in a given block. ```solidity function blockScoreCutoff( uint256 _blockNum ) external view returns ( uint256 _cutoff ); ``` #### Parameters - `_blockNum`: The block for which the cutoff is requested. #### Returns - `_cutoff`: The upper endpoint of the acceptable range of "scores" that providers generate for sortition. A score below the cutoff indicates eligibility to submit updates in the present sortition round. ### currentRewardEpochId Id of the current reward epoch. ```solidity function currentRewardEpochId( ) external view returns ( uint24 ); ``` ### currentScoreCutoff Informational getter concerning the eligibility criterion for being chosen by sortition. ```solidity function currentScoreCutoff( ) external view returns ( uint256 _cutoff ); ``` #### Returns - `_cutoff`: The upper endpoint of the acceptable range of "scores" that providers generate for sortition. A score below the cutoff indicates eligibility to submit updates in the present sortition round. ### currentSortitionWeight Informational getter concerning a provider's likelihood of being chosen by sortition. ```solidity function currentSortitionWeight( address _signingPolicyAddress ) external view returns ( uint256 _weight ); ``` #### Parameters - `_signingPolicyAddress`: The signing policy address of the specified provider. This is different from the sender of an update transaction, due to the signature included in the `FastUpdates` type. #### Returns - `_weight`: The specified provider's weight for sortition purposes. This is derived from the provider's delegation weight for the FTSO, but rescaled against a fixed number of "virtual providers", indicating how many potential updates a single provider may make in a sortition round. ### fetchAllCurrentFeeds Public access to the stored data of all feeds. ```solidity function fetchAllCurrentFeeds( ) external view returns ( bytes21[] _feedIds, uint256[] _feeds, int8[] _decimals, uint64 _timestamp ); ``` #### Returns - `_feedIds`: The list of feed ids. - `_feeds`: The list of feeds. - `_decimals`: The list of decimal places for feeds. - `_timestamp`: The timestamp of the last update. ### fetchCurrentFeeds Public access to the stored data of each feed, allowing controlled batch access to the lengthy complete data. Feeds should be sorted for better performance. ```solidity function fetchCurrentFeeds( uint256[] _indices ) external view returns ( uint256[] _feeds, int8[] _decimals, uint64 _timestamp ); ``` #### Parameters - `_indices`: Index numbers of the feeds for which data should be returned, corresponding to `feedIds` in the `FastUpdatesConfiguration` contract. #### Returns - `_feeds`: The list of data for the requested feeds, in the same order as the feed indices were given (which may not be their sorted order). - `_decimals`: The list of decimal places for the requested feeds, in the same order as the feed indices were given (which may not be their sorted order). - `_timestamp`: The timestamp of the last update. ### numberOfUpdates The number of updates submitted in each block for the last `_historySize` blocks (up to `MAX_BLOCKS_HISTORY`). ```solidity function numberOfUpdates( uint256 _historySize ) external view returns ( uint256[] _noOfUpdates ); ``` #### Parameters - `_historySize`: The number of blocks for which the number of updates should be returned. #### Returns - `_noOfUpdates`: The number of updates submitted in each block for the last `_historySize` blocks. The array is ordered from the current block to the oldest block. ### numberOfUpdatesInBlock The number of updates submitted in a block - available only for the last `MAX_BLOCKS_HISTORY` blocks. ```solidity function numberOfUpdatesInBlock( uint256 _blockNumber ) external view returns ( uint256 _noOfUpdates ); ``` #### Parameters - `_blockNumber`: The block number for which the number of updates should be returned. #### Returns - `_noOfUpdates`: The number of updates submitted in the specified block. ### submissionWindow The submission window is a number of blocks forming a "grace period" after a round of sortition starts, during which providers may submit updates for that round. In other words, each block starts a new round of sortition and that round lasts `submissionWindow` blocks. ```solidity function submissionWindow( ) external view returns ( uint8 ); ``` ### submitUpdates The entry point for providers to submit an update transaction. ```solidity function submitUpdates( struct IFastUpdater.FastUpdates _updates ) external; ``` #### Parameters - `_updates`: Data of an update transaction, which in addition to the actual list of updates, includes the sortition credential proving the provider's eligibility to make updates in the also-included sortition round, as well as a signature allowing a single registered provider to submit from multiple EVM accounts. ## Events ### FastUpdateFeedRemoved Event emitted when a feed is removed. ```solidity event FastUpdateFeedRemoved( uint256 index ) ``` ### FastUpdateFeedReset Event emitted when a feed is added or reset. ```solidity event FastUpdateFeedReset( uint256 votingRoundId, uint256 index, bytes21 id, uint256 value, int8 decimals ) ``` ### FastUpdateFeeds Event emitted at the start of a new voting epoch - current feeds' values and decimals. ```solidity event FastUpdateFeeds( uint256 votingEpochId, uint256[] feeds, int8[] decimals ) ``` ### FastUpdateFeedsSubmitted Event emitted when a new set of updates is submitted. ```solidity event FastUpdateFeedsSubmitted( uint32 votingRoundId, address signingPolicyAddress ) ``` ## Structures ### FastUpdates Fast update structure ```solidity struct FastUpdates { uint256 sortitionBlock; struct SortitionCredential sortitionCredential; bytes deltas; struct IFastUpdater.Signature signature; } ``` ### Signature Signature structure ```solidity struct Signature { uint8 v; bytes32 r; bytes32 s; } ``` --- ## IFastUpdatesConfiguration Interface for the block-latency feed configuration. Sourced from `IFastUpdatesConfiguration.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFastUpdatesConfiguration.sol). ## Functions ### getFeedConfigurations Returns the feed configurations, including removed ones. ```solidity function getFeedConfigurations( ) external view returns ( struct IFastUpdatesConfiguration.FeedConfiguration[] ); ``` ### getFeedId Returns the feed id at a given index. Removed (unused) feed index will return bytes21(0). ```solidity function getFeedId( uint256 _index ) external view returns ( bytes21 _feedId ); ``` #### Parameters - `_index`: The index. #### Returns - `_feedId`: The feed id. ### getFeedIds Returns all feed ids. For removed (unused) feed indices, the feed id will be bytes21(0). ```solidity function getFeedIds( ) external view returns ( bytes21[] ); ``` ### getFeedIndex Returns the index of a feed. ```solidity function getFeedIndex( bytes21 _feedId ) external view returns ( uint256 _index ); ``` #### Parameters - `_feedId`: The feed id. #### Returns - `_index`: The index of the feed. ### getNumberOfFeeds Returns the number of feeds, including removed ones. ```solidity function getNumberOfFeeds( ) external view returns ( uint256 ); ``` ### getUnusedIndices Returns the unused indices - indices of removed feeds. ```solidity function getUnusedIndices( ) external view returns ( uint256[] ); ``` ## Events ### FeedAdded Event emitted when a feed is added. ```solidity event FeedAdded( bytes21 feedId, uint32 rewardBandValue, uint24 inflationShare, uint256 index ) ``` ### FeedRemoved Event emitted when a feed is removed. ```solidity event FeedRemoved( bytes21 feedId, uint256 index ) ``` ### FeedUpdated Event emitted when a feed is updated. ```solidity event FeedUpdated( bytes21 feedId, uint32 rewardBandValue, uint24 inflationShare, uint256 index ) ``` ## Structures ### FeedConfiguration The feed configuration struct. ```solidity struct FeedConfiguration { bytes21 feedId; uint32 rewardBandValue; uint24 inflationShare; } ``` --- ## IFeeCalculator Interface for calculating block-latency feed fees. Sourced from `IFeeCalculator.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFeeCalculator.sol). ## Functions ### calculateFeeByIds Calculates a fee that needs to be paid to fetch feeds' data, addressed by feed id. ```solidity function calculateFeeByIds( bytes21[] _feedIds ) external view returns ( uint256 _fee ); ``` #### Parameters - `_feedIds`: List of feed ids. ### calculateFeeByIndices Calculates a fee that needs to be paid to fetch feeds' data, addressed by index. The index is the position of the feed id in the [`FastUpdatesConfiguration`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFastUpdatesConfiguration.sol) contract — use this overload together with `IFastUpdatesConfiguration.getFeedIndex(bytes21)` when working with FastUpdates. ```solidity function calculateFeeByIndices( uint256[] _indices ) external view returns ( uint256 _fee ); ``` #### Parameters - `_indices`: Indices of the feeds, corresponding to feed ids in the `FastUpdatesConfiguration` contract. --- ## IFtsoFeedIdConverter Interface for converting feed names to feed ids. Sourced from `IFtsoFeedIdConverter.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFtsoFeedIdConverter.sol). ## Functions ### getFeedCategoryAndName Returns the feed category and name for given feed id. ```solidity function getFeedCategoryAndName( bytes21 _feedId ) external pure returns ( uint8 _category, string _name ); ``` #### Parameters - `_feedId`: Feed id. #### Returns - `_category`: Feed category. - `_name`: Feed name. ### getFeedId Returns the feed id for given category and name. ```solidity function getFeedId( uint8 _category, string _name ) external view returns ( bytes21 ); ``` #### Parameters - `_category`: Feed category. - `_name`: Feed name. #### Returns - `_feedId`: Feed id. --- ## FDC The **F**lare **D**ata **C**onnector (FDC) is an [enshrined oracle](/support/terminology#enshrined-oracle) designed to validate external data for Flare's EVM state. It allows users to submit attested data that smart contracts can trust, eliminating the need for direct reliance on users for data integrity. ## Key Features - **Network-Level Security**: Data attestation requires reaching a 50%+ signature weight from data providers, ensuring decentralized consensus. - **Efficient Onchain Storage**: Verified data is stored in a Merkle tree, with only the Merkle root stored onchain to minimize costs. - **Easy Offchain Accessibility**: Data providers serve attestation responses and Merkle proofs offchain through a Data Availability (DA) Layer, ensuring efficient data retrieval. - **Proof-Based Verification**: Smart contracts validate Merkle proofs against the stored Merkle root, ensuring only authentic data triggers contract actions. - **Extensible Attestation Types**: New attestation types and data sources can be added through provider consensus, ensuring adaptability. - **Current State**: A proof can only be constructed for current states (transactions, API responses...), where a current state is a state no older than 14 days. - **Proof Availability**: Once the proof is constructed it remains available indefinitely. - **Security**: Although bitvoting information is public, it is insufficient to construct the Merkle proof without doing the verification work. ## Architecture 1. **Data Verification**: The FDC verifies user-submitted attestation requests. 2. **Merkle Representation**: Verified responses are organized into a Merkle tree, with only the Merkle root stored onchain. 3. **Attestation Usage**: Users retrieve attestation responses and Merkle proofs from data providers and submit them to smart contracts. :::tip[Interested in learning more?] For a detailed explanation of the FDC mechanism, read the [FDC whitepaper](/pdf/whitepapers/20240224-FlareDataConnector.pdf). ::: ### Data Availability Layer The Data Availability Layer (DA Layer) provides API endpoints for querying offchain attestation data. Accessing this data is trustless, as users can independently compute and compare Merkle roots against the onchain version. Operating a DA Layer is permissionless—anyone can run a DA Layer service by sourcing data from an [Flare Entity](/run-node/flare-entity). ## Attestation Types FDC currently supports seven attestation types, each serving specific verification needs. Below is an overview of these types, with detailed explanations available in their respective sections: - **AddressValidity**: Validates the format and checksum of addresses on specified chains. - **EVMTransaction**: Verifies and retrieves transaction details from EVM-compatible chains (`ETH`, `FLR`, `SGB`). - **Web2Json**: Fetches and processes any Web2 data using a JQ transformation, then returns it as ABI-encoded output. (Currently only on Coston & Coston2). - **Payment**: Confirms and fetches payment transaction details from non-EVM chains (`BTC`, `DOGE`, `XRP`). - **ConfirmedBlockHeightExists**: Verifies block existence and confirmation status. - **BalanceDecreasingTransaction**: Validates if a transaction reduces an address's balance. - **ReferencedPaymentNonexistence**: Confirms the absence of specific payments within time intervals. The first three attestation types are the most generally useful, while the last three are primarily used in **FAssets**. ## Workflow Overview For all attestation types, the workflow follows the same general steps. Due to the waiting period between steps **2** and **3**, where the round is finalized, the process is split into multiple sections. ### General Workflow 1. **Request Submission**: Users submit attestation requests to the [`FdcHub`](/fdc/reference/IFdcHub) smart contract. 2. **Batch Processing**: Data providers group requests based on emission timestamps. 3. **Data Retrieval**: Providers fetch and format responses, creating a Merkle tree of hashed responses. 4. **Consensus Storage**: Once signatures representing 50%+ weight are collected, the Merkle root is submitted to the [`Relay`](/network/fsp/solidity-reference/IRelay) contract. 5. **Proof Retrieval**: Users fetch attestation responses and proofs from the DA Layer. 6. **Verification and Action**: Smart contracts verify proofs and use the attested data if valid. ### User Workflow 1. **Identify Data Needs**: Determine the required attestation type and data source. 2. **Prepare Request**: Format the request with the expected response hash (MIC). 3. **Submit Request**: Use [`requestAttestation`](/fdc/reference/IFdcHub#requestattestation) from `FdcHub` and pay the required fee. 4. **Track Submission**: Record the block timestamp and calculate the voting round. 5. **Wait for Finalization**: The voting round concludes, and a relay contract event signals finalization. 6. **Fetch Data**: Retrieve responses and proofs from the DA Layer. 7. **Submit to Smart Contract**: Provide responses and proofs for verification. ### Smart Contract Workflow 1. **Define Triggers**: Establish data-driven triggers. 2. **Receive Data**: Accept attestation responses and proofs from users. 3. **Verify Proofs**: Use the [`FdcVerification`](/fdc/reference/IFdcVerification) contract to validate the response against the Merkle root. 4. **Act on Data**: Utilize the verified data for computation or decision-making. ### Data Provider Workflow 1. **Collect Requests**: Group requests by emission timestamps. 2. **Retrieve Data**: Fetch responses from verifier servers. 3. **Validate Responses**: Ensure data validity using MIC and LUT checks. 4. **Submit BitVectors**: Indicate valid requests using BitVectors during the "choose phase" (90-135 seconds into the round). 5. **Achieve Consensus**: Aggregate BitVectors to form a consensus BitVector. 6. **Create Merkle Tree**: Construct a Merkle tree from validated responses. 7. **Sign and Submit**: Collect signatures representing 50%+ weight and submit the Merkle root to the [`Relay`](/network/fsp/solidity-reference/IRelay) contract. 8. **Serve Data**: Provide attestation responses and proofs via the DA Layer. The **Flare Data Connector (FDC)** is a critical infrastructure component for the Flare ecosystem, enabling smart contracts to access **secure, attested external data** while maintaining trustless verification through **Merkle proofs**. By integrating the FDC, developers can create more **reliable, decentralized applications** across multiple blockchains. ## Watch the video --- ## Getting Started(Fdc) The Flare Data Connector (FDC) is a powerful cross-chain protocol that enables smart contracts on Flare to securely access and verify data from other blockchains. This section demonstrates how to bridge data across chains and attest to events on EVM networks, with practical examples using the Ethereum testnet (Sepolia) and Flare Network. :::info[New to smart contract development?] Learn how to [deploy your first smart contract](/network/getting-started) on Flare before you start this guide, or explore the [official starter kits](/network/guides/hardhat-foundry-starter-kit) for Hardhat and Foundry. ::: At its core, FDC enables any smart contract on Flare to query immutable, verifiable information from supported blockchain networks. The protocol achieves consensus through the BitVote-reveal mechanism within the Flare Systems Protocol suite, allowing dapps to validate external blockchain data using Merkle proofs. Currently supported networks include: - **Non smart-contract**: Bitcoin, Dogecoin, and XRP Ledger (including their testnets) - **Smart-contract**: Ethereum, Songbird, and Flare (including Sepolia, Songbird Testnet Coston, and Flare Testnet Coston2) The protocol's extensible design allows for future integration of additional blockchains and attestation types, making it a foundation for cross-chain interoperability. ## Process overview This guide demonstrates how to use the [EVMTransaction](/fdc/attestation-types/evm-transaction) attestation type to verify and utilize transaction data from external EVM chains on Flare. You'll create a smart contract and accompanying script that interact with the FDC to verify Ethereum transactions and decode their event data. Here's how the attestation process works: 1. **Identify the transaction** For this guide, we'll use an existing transaction on the Sepolia testnet that contains event data we want to verify on Flare. In a real dapp, you might identify transactions based on user actions or specific event emissions. 2. **Prepare the attestation request** To prepare the attestation request, transaction data must be encoded in a FDC-compatible format. While this can be done manually, we'll use Flare's verifier service for simplicity. Note that while Flare provides rate-limited verifiers suitable for development, production applications should use their own verifier service. 3. **Submit the attestation request** Once encoded, the attestation request is submitted to the FDC, initiating the consensus protocol. After consensus is reached, FDC stores the Merkle root of the attested data on the Flare network. 4. **Extract proof and data** After the Merkle root is stored onchain, we'll use the Data Availability (DA) Layer service to retrieve the complete transaction data for our smart contract logic and the Merkle proof needed to verify the data's authenticity. 5. **Verify and use the data** Our smart contract will then verify that the provided transaction data matches what was attested in the Merkle root. Once verified, it will decode the event log data and integrate it into the contract's logic, enabling secure cross-chain data flow in your applications. ## Identify the transaction For this guide, we'll use a pre-existing transaction on the Sepolia testnet: [`0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c`](https://sepolia.etherscan.io/tx/0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c). This transaction is particularly useful for our demonstration as it contains both an ERC20 `Transfer` event and a `Swap` event, providing clear examples of cross-chain event verification. :::info[Confirmation Requirements] Each blockchain connected to FDC has specific confirmation requirements that must be met before data can be attested. For EVM chains, you can configure the required number of confirmations based on the chain's finality and security guarantees. See the [connected blockchain documentation](/fdc/attestation-types/confirmed-block-height-exists#finality) for detailed requirements. ::: :::info[Mainnets and testnets] The Data Connector operates in separate environments for mainnets and testnets, when working with testnets: - Use different base URLs for the attestation client and DA Layer - Specify `testETH` instead of `ETH` as the source network name in transaction encoding - All other procedures and code remain consistent across environments ::: ## Prepare the attestation request To attest to transaction data, we need to encode it in a format that the Flare Data Connector (FDC) can process. This is done through a verifier service. While you can set up your own verifier, we'll use Flare's testnet verifier service available at `https://fdc-verifiers-testnet.flare.network/`. You can explore the API through their Swagger interface at `https://fdc-verifiers-testnet.flare.network/verifier/api-doc`. ### Request structure To prepare an attestation request, you can use the `prepareRequest` endpoint with the following JSON structure: ```json { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "requestBody": { "transactionHash": "0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] } } ``` The request contains three main components: - `attestationType`: Specifies "EVMTransaction" as a 32-byte padded hex string. - `sourceId`: Identifies the source chain ("testETH" for Sepolia testnet) as a 32-byte padded hex string. - `requestBody`: Contains transaction-specific parameters including: - `transactionHash`: Transaction hash to verify. - `requiredConfirmations`: Number of required confirmations. - `provideInput`: Boolean specifying if the input data of the toplevel transaction should be included in the response. - `listEvents`: Flags for including transaction input and event logs. - `logIndices`: Optional log indices (maximum 50 logs per request). For full details, see the [EVMTransaction](/fdc/attestation-types/evm-transaction) type specification. ### Implementation example Here's a TypeScript script that prepares the attestation request: {PrepareRequest} ### Verifier response Upon successful validation, the verifier returns: ```json { "status": "VALID", "abiEncodedRequest": "0x45564d5472616e73616374696f6e00000000000000000000000000000000000074657374455448000000000000000000000000000000000000000000000000009d410778cc0b2b8f1b8eaa79cbd0eed5d3be7514dea070e2041ad00a4c6e88f800000000000000000000000000000000000000000000000000000000000000204e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000" } ``` - `status`: Indicates that the verifier recognized this attestation request as valid. - `abiEncodedRequest`: Contains all the data necessary for the FDC attestation providers to confirm this request. This encoded request can now be submitted to the FDC contract. The attestation clients will pick up the request and include it in the next FDC consensus round. If consensus is reached, your attestation will be included in that round's Merkle root, making it available for use. If consensus fails, you'll need to resubmit the request.
Understanding the structure of `abiEncodedRequest`. The structure of `abiEncodedRequest` may seem complex, but it's essentially a concatenated hex string (with the initial 0x removed) representing different parts of the request. Each part is 32 bytes long (64 characters in hex). Here's a breakdown of the string: ```text 45564d5472616e73616374696f6e000000000000000000000000000000000000 7465737445544800000000000000000000000000000000000000000000000000 9d410778cc0b2b8f1b8eaa79cbd0eed5d3be7514dea070e2041ad00a4c6e88f8 0000000000000000000000000000000000000000000000000000000000000020 4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000000 ``` You can decode the first two parts using an online tool like [playcode.io](https://playcode.io/1752890). Breaking it down line-by-line: - **First line:** `toHex("EVMTransaction")` - **Second line:** `toHex("testETH")` - **Third line:** Message Integrity Code (MIC). This is a hash of the whole response salted with a string `Flare`. It ensures the integrity of the attestation and prevents tampering. - **Remaining lines:** ABI encoded request body (as solidity struct). The structure of the body is defined in the accompanying attestation [type specification](https://github.com/flare-foundation/songbird-state-connector-protocol/blob/main/contracts/interface/types/EVMTransaction.sol#L68). As we supply a list, the encoding is a bit more complicated, but you can easily spot the `transactionHash` as `4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c`.
## Submit the attestation request Once we have our encoded attestation request, we'll submit it to the Flare Data Connector (FDC) smart contract through the `requestAttestation` method on [FDCHub](/fdc/reference/IFdcHub). This broadcasts our request to the network and initiates the verification process. The attestation will be processed in the current FDC round, which typically finalizes within 90-180 seconds. :::info While you can retrieve a proof before round finalization, it won't be valid until the round completes and its Merkle root is stored onchain. ::: Here's how to submit the request and calculate its `roundId`: {SubmitRequest} After submitting the request, wait for round finalization before proceeding to proof extraction and verification. ## Extract proof and data Once the FDC round is finalized and its Merkle root is stored onchain, we can retrieve the full data and proof for our attestation request. The Data Availability (DA) Layer API provides a streamlined way to access this information. ### Using the DA Layer API While a rate-limited public endpoint [is available](/fdc/reference/data-availability-api), you should set up your [own DA Layer service](https://github.com/flare-foundation/data-availability) for production use. ```json { "roundId": FDC_ROUND_ID, "requestBytes": "0xABI_ENCODED_REQUEST" } ``` We are providing the same `abiEncodedRequest` that we used to request the attestation, and the `roundId` that we calculated when we submitted the request. Here's how to retrieve the proof and data: {GetProof} ### Response structure The API returns two key components: - `response`: Contains the complete transaction data, including: - Attestation type and source chain - Transaction details (block number, timestamp, addresses) - Input data and execution status - Emitted events and their details - `proof`: Contains the Merkle proof array, verifying that the data exists in the round's Merkle tree Here's a simplified example of the response structure: ```json { "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "859315", "lowestUsedTimestamp": "1735543584", "requestBody": { "transactionHash": "0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "7384262", "timestamp": "1735543584", "sourceAddress": "0x70ad32b82b4fe2821c798e628d93645218e2a806", "isDeployment": false, "receivingAddress": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", "value": "61000000000000000", "input": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772521a00000000000000000000000000000000000000000000000000000000000000040b000604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57ab7e35b95000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a80600000000000000000000000000000000000000000000000000000000ad2090e40c", "status": "1", "events": [ { "logIndex": 63, "emitterAddress": "0xfff9976782d46cc05630d1f6ebab18b2324d6b14", "topics": [ "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", "0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad" ], "data": "0x00000000000000000000000000000000000000000000000000d8b72d434c8000", "removed": false } // Additional events... ] } }, "proof": [ "0x54124eb68914f7ef9017f47328b02af8a61bc9ed4e276d9e09c725df2056b38e", "0x2ee26beac9f7da0cea28ba8b13f49ca8f6477bb82d839ca1e808ceac2d551427", "0xf8265e7b0c7165ba16111fbf8d1f0e2e279e44b77ff343393fd2269353f2adfa" ] } ``` This data is now ready to be used in your smart contract to: - Verify the data's authenticity using the Merkle proof - Process the transaction data and event logs for your contract's logic ## Verify and use the data Let's examine how to verify and utilize the data from the DA Layer API in your smart contract. We'll focus on a practical example: listening for and verifying USDC transfer events. ### Data structure The response data maps directly to the [`IEVMTransaction`](/fdc/reference/IEVMTransaction) interface, which is already included in both Hardhat and Foundry packages. Here's what you'll work with: - `requestBody`: Contains your original attestation request parameters - `responseBody`: Contains the verified transaction data: - Block details (number, timestamp) - Transaction details (addresses, value, status) - Emitted events (logs, topics, data) Here's a simplified version of the key response structures: ```solidity struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } struct ResponseBody { uint64 blockNumber; uint64 timestamp; address sourceAddress; bool isDeployment; address receivingAddress; uint256 value; bytes input; uint8 status; Event[] events; } struct Event { uint32 logIndex; address emitterAddress; bytes32[] topics; bytes data; bool removed; } ``` ### Implementation example The response consists of several key components: 1. `requestBody`: Contains an exact copy of your original attestation request data. 2. `metadata`: Includes verification-critical information: - `votingRound`: Identifies the specific FDC consensus round - `lowestUsedTimestamp`: Ensures data freshness and proper round assignment 3. `responseBody`: Contains the verified transaction details: - Basic information: block number, timestamp, addresses, value - Transaction status and input data - Complete list of emitted events, each containing: - Log index and emitter address - Event topics and data - Chain reorganization status flag {FDCTransferEventListener} {/* prettier-ignore */} Open in Remix :::warning Don't forget to set the EVM version to `cancun` in Remix before compiling the contract. ::: ### Using the contract 1. **Proof Verification** The contract uses the `ContractRegistry` library to access Flare's official verifiers. The verification process: - Retrieves the current verifier through the Flare governance-managed registry - Uses `isEVMTransactionProofValid` to verify the Merkle proof and data integrity - Requires successful verification before proceeding with any data processing 2. **Event Processing** After verification, the `collectTransferEvents` function handles the business logic: - Processes the verified transaction data - Filters for USDC Transfer events - Decodes and stores relevant event data This two-phase approach provides robust security against malicious data providers: - While the data comes from an offchain source (DA Layer API), it must match the onchain Merkle root - Any attempt to provide manipulated data will fail at the proof verification stage - Only data that has achieved consensus through the FDC protocol can pass verification To use the contract, simply retrieve the proof from the DA Layer API and submit it: {VerifyProof} ## Wait for round finalization (optional) Before using a proof, you must ensure the FDC round has been finalized and its Merkle root accepted. Here are the recommended approaches for different scenarios: **Production environment:** Use the `Relay` contract's event system: - Access the latest `Relay` contract through `ContractRegistry` - Listen for the `ProtocolMessageRelayed` event with: - `protocolId`: 200 (FDC protocol identifier) - `roundId`: Your submitted round ID **Testing environment:** For testing, you can use the `Relay` contract's view method ```solidity isFinalized(uint256 _protocolId, uint256 _votingRoundId) returns (bool) ``` ## Watch the video --- ## Attestation Types **FDC Attestations** provide cryptographic proofs for data originating outside Flare's EVM state. They enable smart contracts to verify external data trustlessly. For example, FDC attestations can validate: - **Non-Payment Verification:** Confirm whether a payment **has not been made** on a UTXO chains like Bitcoin or Dogecoin. - **Event Log Authentication:** Verify event logs generated by transactions on EVM-compatible blockchains. export const AttestationTypeLists = () => { const items = useCurrentSidebarCategory().items; const isXrp = (item) => /\/xrp-payment(-nonexistence)?$/.test(item.docId ?? item.href ?? ""); const general = items.filter((item) => !isXrp(item)); const xrp = items.filter(isXrp); return ( <> General attestation types XRPL specific attestation types ); }; FDC currently supports the following attestation types: --- ## FDC Reference ## Deployed Contracts export const contracts = [ "FdcHub", "FdcVerification", "FdcRequestFeeConfigurations", "FdcInflationConfigurations", ]; ## Interfaces --- ## Troubleshooting This page covers common issues developers encounter when working with the Flare Data Connector (FDC) and how to resolve them. ## DA Layer Returns 400 Bad Request One of the most common issues developers face is receiving a `400 Bad Request` error from the Data Availability (DA) Layer when trying to retrieve a proof. ### Symptoms - Your attestation request is prepared successfully by the verifier (status: `VALID`). - The request is submitted to the FDC Hub and a transaction hash is returned. - The voting round finalizes without errors. - When you query the DA Layer for the proof, you receive a `400 Bad Request` error. ### Root Cause: Attestation Consensus Failure This error occurs when the attestation providers could not reach consensus on your request. For an attestation to be included in a voting round, a majority of providers must independently verify the same data and agree on the response. The most common cause is **non-deterministic API responses**—when the data source returns different values to different attestation providers. ### Why This Happens with Web2Json When using the `Web2Json` attestation type, each attestation provider independently queries your specified API endpoint. If the API returns values that change between requests (even by small amounts), providers will compute different response hashes. Since the hashes don't match, consensus cannot be reached, and the attestation is not included in the Merkle tree. **Example scenario:** 1. Provider A queries the API at timestamp T and receives: `{"value": "44,442,373.09"}` 2. Provider B queries the API at timestamp T+2s and receives: `{"value": "44,442,375.12"}` 3. Provider C queries the API at timestamp T+5s and receives: `{"value": "44,442,380.00"}` Each provider computes a different hash, consensus fails, and your proof is not available. ### Solution: Stabilize Your API Response To ensure consensus, your API response must be deterministic—all providers must receive the exact same data. Here are several approaches: #### 1. Round Values in Your JQ Filter Use the `postProcessJq` filter to round values to a coarser granularity that won't change between provider queries. **Before (exact values, consensus-breaking):** ```json { "postProcessJq": "{amount: .value | gsub(\",\"; \"\") | tonumber}" } ``` **After (rounded to nearest $100k, consensus-safe):** ```json { "postProcessJq": "{amount: (.value | split(\",\") | join(\"\") | split(\".\") | .[0] | .[:-5] + \"00000\")}" } ``` This transforms `"44,442,373.09"` → `"44400000"`, which remains stable even if the exact value fluctuates. #### 2. Use Timestamp-Based API Endpoints If you control the API, provide an endpoint that returns data for a specific timestamp or block. This ensures all providers query the same historical state. **Example:** ``` https://api.example.com/reserves?timestamp=1700000000 ``` #### 3. Use Snapshot Endpoints Design your API to return values that only update at specific intervals (e.g., hourly, daily). Include the snapshot timestamp in the response so providers know they're querying the same data point. **Example response:** ```json { "snapshot_time": "2024-02-05T00:00:00Z", "value": "44,400,000.00" } ``` #### 4. Extract Only Stable Fields If the API returns multiple fields, use `postProcessJq` to extract only the fields that don't change frequently. **Example:** ```json { "postProcessJq": "{status: .status, lastUpdated: .metadata.lastUpdated}" } ``` ### Debugging Checklist 1. **Test your JQ filter locally:** ```bash curl -s "https://your-api.com/endpoint" | jq '{your: .filter}' ``` Run this multiple times and verify you get identical output. 2. **Check the voting round on the Systems Explorer:** Visit `https://coston2-systems-explorer.flare.rocks/voting-round/{roundId}?tab=fdc` to see if any attestations were included. If your round shows "0 attestation requests," your request wasn't included. 3. **Verify the verifier accepts your request:** ```bash curl -X POST "https://fdc-verifiers-testnet.flare.network/verifier/web2/Web2Json/prepareRequest" \ -H "Content-Type: application/json" \ -d '{"attestationType": "0x...", "sourceId": "0x...", "requestBody": {...}}' ``` A `VALID` status means the request format is correct, but doesn't guarantee consensus. 4. **Check API accessibility:** Ensure your API endpoint is publicly accessible and not rate-limited. Attestation providers must be able to reach it from their infrastructure. ## Query Parameters in URL Cause Consensus Failure ### Symptoms - Your `Web2Json` attestation request passes verifier preparation (status: `VALID`). - The DA Layer returns a `400 Bad Request` when retrieving the proof, or consensus fails silently. ### Root Cause: Query Parameters Embedded in the URL The [`Web2Json`](/fdc/attestation-types/web2-json) attestation type provides a dedicated `queryParams` field for query parameters. If you include query parameters directly in the `url` field (e.g., `https://api.example.com/data?key=value`), attestation providers may not process the request correctly, leading to consensus failure. ### Solution: Use the `queryParams` Field Move all query parameters from the URL into the `queryParams` field as a stringified JSON object. **Before (query parameters in URL — incorrect):** ```json { "url": "https://api.example.com/reserves?timestamp=1700000000&format=json", "queryParams": "{}" } ``` **After (query parameters in separate field — correct):** ```json { "url": "https://api.example.com/reserves", "queryParams": "{\"timestamp\":\"1700000000\",\"format\":\"json\"}" } ``` This ensures all attestation providers construct the request identically, which is required for consensus. ## Attestation Request Rejected by Verifier If the verifier returns an error when preparing your request, check the following: ### Invalid JQ Filter The FDC verifier uses a limited subset of JQ. Some operations like `tonumber` or arithmetic may not work as expected. **Workaround:** Return values as strings and parse them in your smart contract. ### API Not Whitelisted For `Web2Json` attestations, the API domain must be whitelisted by the Flare Network. On testnets, common APIs like GitHub Pages and public REST APIs are typically allowed. Contact the Flare team if you need a specific domain whitelisted for production use. ### Malformed ABI Signature Ensure your `abiSignature` field is valid JSON and matches the Solidity struct you'll use to decode the data. **Common mistakes:** - Missing quotes around field names - Using `int` instead of `int256` - Incorrect nesting for complex structs ## Proof Verification Fails Onchain If your proof retrieves successfully but fails verification in your smart contract: ### Merkle Proof Mismatch Ensure you're passing the proof to the correct verification function for your attestation type. ```solidity // For Web2Json bool valid = fdcVerification.verifyWeb2Json(proof); ``` ### Response Data Mismatch The response data you submit must exactly match what was attested. Don't modify or re-encode the data after retrieving it from the DA Layer. ### Wrong Voting Round Ensure you're querying the correct voting round ID. The round ID is calculated from the block timestamp when you submitted the request. ## Best Practices 1. **Design for consensus from the start.** When integrating external APIs, always consider how to ensure deterministic responses. 2. **Use coarse granularity for frequently-changing values.** Round to the nearest unit that makes sense for your use case (e.g., nearest $1000 for financial data). 3. **Test on testnet first.** The testnet FDC behaves identically to mainnet but allows for faster iteration. 4. **Monitor your attestation requests.** Use the Systems Explorer to track whether your requests are being included in voting rounds. 5. **Cache proofs appropriately.** Once a proof is generated, it remains valid indefinitely. Cache successful proofs to avoid redundant queries. --- ## AddressValidity Assertion whether a given string represents a **valid address** on an external blockchain. ## Supported chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet v3), `testDOGE`, `testXRP` | ## Request | Field | Solidity Type | Description | | ------------ | ------------- | ----------------------------- | | `addressStr` | `string` | The address string to verify. | ## Response | Field | Solidity Type | Description | | --------------------- | ------------- | ----------------------------------------------------------------------------------------------- | | `isValid` | `bool` | Indicates whether the provided address is valid. | | `standardAddress` | `string` | The standardized form of the validated address if `isValid`; otherwise, an empty string. | | `standardAddressHash` | `bytes32` | The `keccak256` hash of the `standardAddress` if `isValid`; otherwise, a zero `bytes32` string. | ## Verification process The address is verified against the validity criteria specific to the chain identified by `sourceId`. If the address meets all criteria: 1. `isValid` is set to `true`. 2. The `standardAddress` and its `standardAddressHash` are computed. If the address is invalid: - `isValid` is set to `false`. - The `standardAddress` is empty and `standardAddressHash` is zero value. :::note[Lowest used timestamp] For the `lowestUsedTimestamp` parameter, the value `0xffffffffffffffff` (equivalent to $ 2^{64} - 1 $) in hexadecimal) is used as the default. ::: ## Address validity criteria ### Bitcoin An address on Bitcoin is derived from the locking script (`pkscript`). Only standard locking scripts get assigned an address. There are two formats of the Bitcoin addresses, `Base58` and `Bech32(m)`. The format is determined based on the type of locking script. #### `Base58` Bitcoin's **Base58** format uses the following encoding dictionary: ``` 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz ``` ##### Address Structure Decoded addresses have the format: ``` ``` ##### Components - **`leadingByte`**: - On **mainnet**: - `00` for Pay-to-PubKey (p2pk) and Pay-to-PubKey-Hash (p2pkh) - `05` for Pay-to-Script-Hash (p2sh) - On **testnet**: - `6f` for p2pk and p2pkh - `c4` for p2sh - **`hash`**: Represents either the public key, hash of the public key, or hash of a script. - **`checksum`**: The first four bytes of the double SHA-256 hash of ``. ##### Validation Criteria 1. Address contains only characters from the Base58 dictionary. 2. Decoded hexadecimal form is exactly **25 bytes** long (address length varies between **26 to 34 characters**). 3. Starts with a valid `leadingByte`. 4. The checksum is valid and matches the first four bytes of the double SHA-256 hash. ##### Resources - [Base58Check Encoding](https://en.bitcoin.it/wiki/Base58Check_encoding) - [BIP-0013](https://en.bitcoin.it/wiki/BIP_0013) - [BIP-0016](https://en.bitcoin.it/wiki/BIP_0016) #### `Bech32(m)` Bech32 is a newer address format using the character set: ``` qpzry9x8gf2tvdw0s3jn54khce6mua7l ``` ##### Address Structure A Bech32 address has the following components: - **Human-Readable Part (HRP)**: - `bc` for **mainnet** - `tb` for **testnet** - **Separator**: Always `1` - **Data Part**: - The first character indicates the **witness version** (0-16). - The last six characters form a **checksum**. The checksum differs based on the witness version: - **Bech32** for witness version `0` ([BIP-0173](https://en.bitcoin.it/wiki/BIP_0173)) - **Bech32m** for witness versions `1` to `16` ([BIP-0350](https://en.bitcoin.it/wiki/BIP_0350)) ##### Validation Criteria 1. Address contains only characters from the Bech32 dictionary. 2. All non-numeric characters must be either entirely uppercase or lowercase. 3. Starts with a valid HRP followed by the separator (`1`). 4. Length is between **14 to 74 characters**, with the length modulo 8 being **0, 3, or 5**. - For witness version `0`, length must be **42 or 62 characters**. 5. Checksum is validated based on the witness version. 6. Addresses with witness versions `2` and above are unsupported and invalid. ##### Resources - [Bech32](https://en.bitcoin.it/wiki/Bech32) - [SegWit](https://en.bitcoin.it/wiki/Segregated_Witness) - [BIP-0141](https://en.bitcoin.it/wiki/BIP_0141) - [BIP-0341](https://en.bitcoin.it/wiki/BIP_0341) - [BIP-0350](https://en.bitcoin.it/wiki/BIP_0350) ### Dogecoin (`Base58`) Dogecoin uses a **Base58** dictionary, identical to Bitcoin's: ``` 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz ``` #### Address Structure Decoded addresses have the format: ``` ``` #### Components - **`leadingByte`**: - On **mainnet**: - `1e` for p2pk and p2pkh - `16` for p2sh - On **testnet**: - `6f` for p2pk and p2pkh - `71` for p2sh - **`hash`**: Represents either the public key, hash of the public key, or script hash. - **`checksum`**: First four bytes of the double SHA-256 hash of ``. #### Validation Criteria 1. Address contains only characters from the Base58 dictionary. 2. Length is **26 to 34 characters**. Decoded hex form is **25 bytes**. 3. Valid leading byte: - **Mainnet**: Starts with `D`, `A`, or `9`. - **Testnet**: Starts with `n`, `m`, or `2`. 4. The checksum is validated. ### XRPL (`Base58`) The XRP Ledger uses a custom **Base58** dictionary: ``` rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz ``` #### Address Structure Decoded to hexadecimal: ``` ``` #### Components - **`leadingByte`**: - On **mainnet**: `00` (addresses start with `r`). - **`publicKeyHash`**: A **20-byte** hash of the public key. - **`checksum`**: The first four bytes of the double SHA-256 hash of ``. #### Validation Criteria 1. Address contains only characters from the XRPL Base58 dictionary. 2. Length is **25 to 35 characters**. Decoded hex form is **25 bytes**. 3. Address starts with `r`. 4. The checksum is valid. #### Resources - [XRPL Base58 Encodings](https://xrpl.org/docs/references/protocol/data-types/base58-encodings) - [XRPL Addresses](https://xrpl.org/accounts.html#addresses) ## Contract Interface For the complete interface definition, see [`IAddressValidity`](/fdc/reference/IAddressValidity.mdx). --- ## BalanceDecreasingTransaction Information describing a transaction that either **decreases the balance** for a specified address. A transaction is considered "balance decreasing" for the specified address if: 1. The balance **after the transaction** is lower than **before**. 2. The address is among the **signers** of the transaction (even if its balance is not reduced). ## Supported chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet v3), `testDOGE`, `testXRP` | ## Request | Field | Solidity Type | Description | | ------------------------ | ------------- | --------------------------------------------------------------- | | `transactionId` | `bytes32` | Unique ID of the transaction to be verified. | | `sourceAddressIndicator` | `bytes32` | Indicator of the address whose balance may have been decreased. | ## Response | Field | Solidity Type | Description | | -------------------------- | ------------- | ---------------------------------------------------------------------------------------------- | | `blockNumber` | `uint64` | Block number containing the transaction. | | `blockTimestamp` | `uint64` | Timestamp of the block containing the transaction. | | `sourceAddressHash` | `bytes32` | Standard hash of the address indicated by `sourceAddressIndicator`. | | `spentAmount` | `int256` | Amount spent by the source address in minimal units (can be negative). | | `standardPaymentReference` | `bytes32` | Standard payment reference of the transaction. Zero value if the transaction has no reference. | :::warning[Standard Payment Reference] If a transaction has no `standardPaymentReference`, it is set to default value, thus, zero value reference should be used with caution. ::: ## Verification process 1. The transaction identified by `transactionId` is fetched from the **source blockchain node** or a relevant indexer. 2. If the transaction cannot be fetched or is in a block with insufficient [confirmations](#finality), the attestation request is rejected. 3. Once the transaction is retrieved, the response fields are extracted if it qualifies as **balance decreasing** for the specified address. The verification process is chain-specific and can be computed with a [balance decreasing summary](#balance-decreasing-summary), with details described below. ### UTXO chains (Bitcoin and Dogecoin) #### Address Indication - `sourceAddressIndicator`: Represents the index of the transaction input, formatted as a **0x-prefixed 32-byte string**. - If the specified input does not exist or lacks an associated address, the attestation request is rejected. #### Data Calculation - `sourceAddress`: Address associated with the specified transaction input. - `spentAmount`: Calculated as `Sum of all inputs with sourceAddress - Sum of all outputs with sourceAddress`. The value can be negative. - `blockTimestamp`: The **mediantime** of the block. ### Account-based chains (XRPL) #### Address Indication - `sourceAddressIndicator`: The [standard address hash](#standard-address-hash) of the address in question. - If the indicated address is not among the transaction signers and its balance was not decreased, the attestation request is rejected. #### Data Calculation - `spentAmount`: Difference between the balance of the address **after** and **before** the transaction. Can be negative. - `blockTimestamp`: The **close_time** of the ledger, converted to Unix time. - `standardPaymentReference`: [Standard payment reference] for `Payment` transactions, otherwise zero. :::note[Lowest used timestamp] For the `lowestUsedTimestamp` parameter, the `blockTimestamp` of the transaction is used. ::: ## Balance decreasing summary A balance-decreasing summary analyses a transaction that has decreased or could possibly decrease the balance of an account. A balance-decreasing summary is calculated for a given transaction and source address indicator (`sourceAddressIndicator`). The summary contains the fields as stated in the table below. The interpretation of some fields is chain dependent. Descriptions of these fields are left empty and are later explained for each specific blockchain. For a given transaction and an address indicator, the balance-decreasing summary can only be calculate if the transaction is considered to be balance-decreasing for the indicated address When implemented, the function that calculates the balance-decreasing summary tries to calculate it. If it is successful, it returns a success status and the summary itself. If not, it returns an error status. | Field | Description | | -------------------------- | ---------------------------------------------------------- | | `transactionId` | - | | `transactionStatus` | [Transaction success status](#transaction-success-status). | | `sourceAddress` | - | | `spentAmount` | - | | `standardPaymentReference` | [Standard payment reference](#standard-payment-reference). | The following are detailed descriptions of fields for each supported chain. ### Bitcoin and Dogecoin For Bitcoin and Dogecoin, `sourceAddressIndicator` is the index of a transaction input (in hex zero padded on the left to 0x prefixed 32 bytes). If the input with the given index does not exist or the indicated input does not have an address, no summary is made. In particular, no summary is made for coinbase transactions. | Field | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `transactionId` | The transaction ID found in the field `txid`. For segwit transactions, this is not the same as _hash_. | | `sourceAddress` | Address of the indicated input. | | `spentAmount` | The sum of values of all inputs with `sourceAddress` minus the sum of values of all outputs with `sourceAddress`. Can be negative. | ### XRPL For XRPL, `sourceAddressIndicator` is [standardAddressHash](#standard-address-hash) of the indicated address. If the `sourceAddressIndicator` does not match any of the addresses who signed the transaction or whose balance was decreased by the transaction, the summary is not made. | Field | Description | | --------------- | ------------------------------------------------------------------------------------------------- | | `transactionId` | Hash of the transaction found in the field `hash`. | | `sourceAddress` | Address whose [standardAddressHash](#standard-address-hash) matches the `sourceAddressIndicator`. | | `spentAmount` | The amount for which the balance of the `sourceAddress` has lowered. Can be negative. | ## Standard payment reference A standard payment reference is defined as a 32-byte sequence that can be added to a payment transaction, in the same way that a payment reference is attached to a traditional banking transaction. ### Bitcoin and Dogecoin - Uses `OP_RETURN` to store references. - A transaction is considered to have a `standardPaymentReference` defined if it has: - Exactly one output UTXO with `OP_RETURN` script, and - The script is of the form `OP_RETURN ` or `6a `in hex, where the length of the reference is 32 bytes. - Then `0x` is the `standardPaymentReference`. ### XRPL - Uses the `memoData` field. - A transaction has a `standardPaymentReference` if it has: - Exactly one [Memo](https://xrpl.org/transaction-common-fields.html#memos-field), and - The `memoData` of this field is a hex string that represents a byte sequence of exactly 32 bytes. - This 32-byte sequence defines the `standardPaymentReference`. ## Transaction success status Transactions on different blockchains have various success statuses. Some blockchains may include transactions even if they failed to execute as intended. | Status | Code | | ------------------ | ---- | | `SUCCESS` | 0 | | `SENDER_FAILURE` | 1 | | `RECEIVER_FAILURE` | 2 | **Bitcoin and Dogecoin** It is not possible to include an unsuccessful transaction in a Bitcoin or Dogecoin block. Hence, if a transaction is included on a confirmed block, its status is "SUCCESS." **XRPL** On XRPL, some transactions that failed (based on the reason for failure) can be included in a confirmed block. - **`tesSUCCESS`**: Transaction successful. - **`tec`-class codes**: Indicate reasons for failure, such as: - `tecDST_TAG_NEEDED`: Missing required destination tag. - `tecNO_DST`: Nonexistent or unfunded destination address. - `tecNO_PERMISSION`: Source address lacks permission to send funds. ## Standard address hash The **standard address hash** is defined as the `keccak256` hash of the standard address as a string: ```solidity keccak256(standardAddress) ``` :::note[standard address] If an address is case insensitive, the standard address is lowercase. If an address is case sensitive, there is always only one (correct) form of the address. ::: **Examples:** | Chain | Standard Address | Standard Address Hash | | ------------------ | -------------------------------------------- | -------------------------------------------------------------------- | | Bitcoin (`Base58`) | `1FWQiwK27EnGXb6BiBMRLJvunJQZZPMcGd` | `0x8f651b6990a4754c58fcb5c5a11f4d40f8ddfdeb0e4f67cdd06c27f8d7bcbe33` | | Bitcoin (`Bech32`) | `bc1qrmvxmwgqfr5q4fvtvnxczwxwm966n53c4lxh4v` | `0xf75dc4b039ac72e037d67199bb92fa25db32b2210954df99637428473d47cedf` | | Dogecoin | `DL2H9FuaXsxivSs1sRtuJ8uryosyAj62XX` | `0x51064c88c6b8e9d58b2abeae37a773bf89c9b279f8a05fa0ac0e81ebe13d2f4f` | | XRPL | `rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv` | `0xa491aed10a1920ca31a85ff29e4bc410705d37d4dc9e690d4d500bcedfd8078f` | ## Finality Blockchains have varying confirmation depths to consider blocks as final. | Chain | `chainId` | Confirmations required | Confirmation time | | -------- | --------- | ---------------------- | ----------------- | | Bitcoin | 0 | 6 | ≈60 mins | | Dogecoin | 2 | 60 | ≈60 mins | | XRPL | 3 | 3 | ≈12 seconds | ## Contract Interface For the complete interface definition, see [`IBalanceDecreasingTransaction`](/fdc/reference/IBalanceDecreasingTransaction.mdx). --- ## ConfirmedBlockHeightExists Assertion whether a block with the specified `blockNumber` is **confirmed** with additional data to compute the **block production rate** within a given time window. ## Supported chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet v3), `testDOGE`, `testXRP` | ## Request | Field | Solidity Type | Description | | ------------- | ------------- | -------------------------------------------------------------------- | | `blockNumber` | `uint64` | The block number to confirm. | | `queryWindow` | `uint64` | The time period (in seconds) to calculate the block production rate. | ## Response | Field | Solidity Type | Description | | --------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------- | | `blockTimestamp` | `uint64` | The timestamp of the block at `blockNumber`. | | `numberOfConfirmations` | `uint64` | The required number of confirmations for the block to be considered confirmed (chain-specific). | | `lowestQueryWindowBlockNumber` | `uint64` | The block number of the latest block with a timestamp strictly less than `blockTimestamp - queryWindow`. | | `lowestQueryWindowBlockTimestamp` | `uint64` | The timestamp of the block at `lowestQueryWindowBlockNumber`. | ## Verification process 1. The function checks if the block with `blockNumber` is confirmed by at least the required `numberOfConfirmations` for the specified chain. - If the block does not meet this requirement, the request is rejected. - A block at the tip of the chain has exactly **1 confirmation**. 2. The lowestQueryWindowBlock` is identified, and its block number and timestamp are extracted. 3. The required confirmations are defined based on chain-specific [finality](#finality). 4. The returned `timestamp` is: - `mediantime` for Bitcoin and Dogecoin. - `close_time` for XRPL. :::note[Lowest used timestamp] For the `lowestUsedTimestamp` parameter, the value of `lowestQueryWindowBlockTimestamp` is used. ::: ## Finality Blockchains have varying confirmation depths to consider blocks as final. | Chain | `chainId` | Confirmations required | Confirmation time | | -------- | --------- | ---------------------- | ----------------- | | Bitcoin | 0 | 6 | ≈60 mins | | Dogecoin | 2 | 60 | ≈60 mins | | XRPL | 3 | 3 | ≈12 seconds | ## Contract Interface For the complete interface definition, see [`IConfirmedBlockHeightExists`](/fdc/reference/IConfirmedBlockHeightExists.mdx). --- ## EVMTransaction Information about an Ethereum Virtual Machine (EVM) transaction, including details on associated events if specified. ## Supported chains | Network Type | Supported Chains | | ------------ | ---------------------------------------------------------------------------------------------------- | | **Mainnet** | `ETH` (Ethereum), `FLR` (Flare Mainnet), `SGB` (Songbird Canary-Network) | | **Testnet** | `testETH` (Ethereum Sepolia), `testFLR` (Flare Testnet Coston2), `testSGB` (Songbird Testnet Coston) | ## Request | Field | Solidity Type | Description | | ----------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transactionHash` | `bytes32` | The hash of the transaction. | | `requiredConfirmations` | `uint16` | The number of confirmations required for the block containing the transaction to be considered final by the requestor. | | `provideInput` | `bool` | If set to `true`, the `input` data field of the transaction will be included in the response. | | `listEvents` | `bool` | If `true`, events specified by `logIndices` will be included in the response. If `false`, no events are included. | | `logIndices` | `uint32[]` | A list of event indices (`logIndex`) to be relayed if `listEvents` is `true`. Should be sorted by the requestor. Maximum of 50 indices allowed. An empty list indicates all events up to a maximum of 50. If `listEvents` is `false`, this field must be empty. | :::note[Using Event Logs Correctly] - Events (logs) are indexed at the **block level**, not at the transaction level. - The contract using this attestation should define the order of event logs, and the requestor should ensure `logIndices` are sorted according to these specifications. ::: ## Response The response fields align with EVM's [JSON-RPC API](https://ethereum.org/developers/docs/apis/json-rpc/#eth_gettransactionbyhash): | Field | Solidity Type | Description | | ------------------ | ------------- | ----------------------------------------------------------------------------------------------------- | | `blockNumber` | `uint64` | The block number in which the transaction is included. | | `timestamp` | `uint64` | The timestamp of the block in which the transaction is included. | | `sourceAddress` | `address` | The address (`from`) that signed the transaction. | | `isDeployment` | `bool` | Indicates whether the transaction is a contract creation (`true`) or a regular transaction (`false`). | | `receivingAddress` | `address` | The address (`to`) receiving the transaction. Zero address if `isDeployment` is `true`. | | `value` | `uint256` | The value transferred in the transaction, in wei. | | `input` | `bytes` | Transaction input data if `provideInput` is `true`; otherwise, returns `0x00`. | | `status` | `uint8` | Transaction status: `1` for success, `0` for failure. | | `events` | `Event[]` | An array of requested events if `listEvents` is `true`; otherwise, an empty array. | ### `Event` Struct Each `Event` struct represents a log entry similar to EVM event logs: | Field | Solidity Type | Description | | ---------------- | ------------- | ------------------------------------------------------------------------------------------ | | `logIndex` | `uint32` | The index of the event within the block. | | `emitterAddress` | `address` | The address of the contract that emitted the event. | | `topics` | `bytes32[]` | An array of up to four 32-byte strings representing indexed log arguments. | | `data` | `bytes` | Non-indexed log data, concatenated as 32-byte strings. Must be at least 32 bytes long. | | `removed` | `bool` | `true` if the log was removed due to a chain reorganization; `false` if it is still valid. | ## Verification process 1. The function checks if the transaction with the given `transactionHash` is included in a block on the **main chain** with at least the specified `requiredConfirmations`. 2. If the block has insufficient confirmations or if the transaction is not found, the request is rejected. 3. If `listEvents` is enabled and an event specified by `logIndices` does not exist, the request is also rejected. 4. The specified data (transaction details, input data, and events) is retrieved and relayed based on the request parameters. :::note[Event handling] - Ensure `logIndices` are sorted as required by the consuming contract. - If `logIndices` is not empty while `listEvents` is set to `false`, the request will be rejected. - Events are capped at a maximum of **50 entries** to optimize performance. ::: ## Contract Interface For the complete interface definition, see [`IEVMTransaction`](/fdc/reference/IEVMTransaction.mdx). --- ## JsonApi (deprecated) :::danger Since May 2025 this spec is considered deprecated. The `JsonApi` attestation type has been update to a new version, `Web2Json`. You can find the updated version of this spec [here](/fdc/attestation-types/web2-json). ::: Data retrieval from **Web2 JSON APIs** with JQ transformations. This attestation type allows smart contracts to access and process external JSON data in a verifiable way. :::warning[Testnet Only] JsonApi attestation type is currently only available on Flare Testnet Coston2 and Songbird Testnet Coston. ::: ## Supported sources | Network Type | Sources | | ------------ | -------------------------- | | **Web2** | `WEB2` (Web 2.0 JSON APIs) | | **Testnet** | `testWEB2` | ## Request | Field | Solidity Type | Description | | --------------- | ------------- | -------------------------------------------------------------------------------- | | `url` | `string` | URL of the JSON API endpoint to query. | | `postprocessJq` | `string` | JQ filter expression to transform the retrieved JSON data. | | `abi_signature` | `string` | ABI signature defining the struct format for encoding the transformed JSON data. | ## Response | Field | Solidity Type | Description | | ------------------ | ------------- | ------------------------------------------------ | | `abi_encoded_data` | `bytes` | ABI-encoded data result after JQ transformation. | ## Verification Process ### 1. API Request The attestation service performs a GET request to the specified `url`. - If the request fails or returns non-JSON data, the attestation request is rejected. - The service validates that the response is valid JSON data. ### 2. JQ Transformation The specified `postprocessJq` filter is applied to the retrieved JSON: - The JQ filter must be a valid JQ expression. - The filter's output must match the structure defined in `abi_signature`. - If the JQ transformation fails, the attestation request is rejected. ### 3. ABI Encoding The transformed data is ABI-encoded according to the provided `abi_signature`: - The encoding must match the Solidity ABI specification. - The encoded bytes are returned in `abi_encoded_data`. --- ## Payment Information about a transaction on an external chain that is classified as a **native currency payment**. Each supported blockchain specifies how a payment transaction should be formatted to be provable using this attestation type. These provable payments mimic traditional banking transactions where entity A sends a native currency to entity B, with an optional payment reference. ## Supported chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet v3), `testDOGE`, `testXRP` | ## Request | Field | Solidity Type | Description | | --------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transactionId` | `bytes32` | Unique ID of the payment transaction. | | `inUtxo` | `uint256` | For UTXO chains, if the value is less than `2**16`, this field is the index of the transaction input with the source address. Otherwise, it represents the `standardAddressHash` of the input address for which the payment proof will be constructed. For non-UTXO chains, this is always `0`. | | `utxo` | `uint256` | For UTXO chains, if the value is less than `2**16`, this field is the index of the transaction output with the receiving address. Otherwise, it represents the `standardAddressHash` of the output address for which the payment proof will be constructed. For non-UTXO chains, this is always `0`. | ## Response | Field | Solidity Type | Description | | ------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `blockNumber` | `uint64` | The block number in which the transaction is included. | | `blockTimestamp` | `uint64` | The timestamp of the block containing the transaction. | | `sourceAddressHash` | `bytes32` | Standardized address hash of the source address. | | `sourceAddressesRoot` | `bytes32` | The root of the Merkle tree of the source addresses. | | `receivingAddressHash` | `bytes32` | Standardized address hash of the receiving address. Returns a zero 32-byte string if the transaction status is not successful. | | `intendedReceivingAddressHash` | `bytes32` | Standardized address hash of the intended receiving address if the transaction failed. | | `spentAmount` | `int256` | Amount (in minimal units) spent by the source address. | | `intendedSpentAmount` | `int256` | Intended amount (in minimal units) to be spent by the source address, relevant if the transaction status is unsuccessful. | | `receivedAmount` | `int256` | Amount (in minimal units) received by the receiving address. | | `intendedReceivedAmount` | `int256` | Intended amount (in minimal units) to be received by the receiving address if the transaction failed. | | `standardPaymentReference` | `bytes32` | [Standard payment reference](#standard-payment-reference). | | `oneToOne` | `bool` | Indicates if the transaction involves only one source and one receiver. | | `status` | `uint8` | [Transaction success status](#transaction-success-status). | :::warning[Standard Payment Reference] If a transaction has no `standardPaymentReference`, it is set to default value; zero value reference should thus be used with caution. ::: ## Verification Process 1. The transaction identified by `transactionId` is fetched from the relevant blockchain node or indexer. 2. If the transaction cannot be retrieved or is in a block with insufficient [confirmations](#finality), the attestation request is rejected. 3. Once the transaction data is fetched, a [payment summary](#payment-summary) is computed according to the chain-specific rules. - If the payment summary is successfully generated, the response is populated using this data. - If the summary cannot be computed, the attestation request is rejected. 4. The fields `blockNumber` and `blockTimestamp` are extracted from the block data if they are not directly available in the transaction data. - For **Bitcoin** and **Dogecoin**, the `blockTimestamp` is derived from the **mediantime** of the block. - For **XRPL**, the `blockTimestamp` is derived from the **close time** of the ledger, converted to UNIX time. :::note[Lowest used timestamp] For the `lowestUsedTimestamp` parameter, the **`blockTimestamp`** of the transaction is used. ::: ## Payment Summary A **payment summary** consolidates all relevant data about a transaction that represents a payment. This is particularly focused on payments between one source account (address) and one target account. - **UTXO Blockchains (e.g., BTC, DOGE)**: Payments can aggregate inputs from multiple addresses and distribute them to multiple outputs. The summary here is computed based on specified input and output indices that identify addresses of interest. - **XRPL**: Supports various transaction types, but a payment summary is only fully calculated for transactions of type `Payment`. ### Structure The summary includes the fields detailed in the table below. The interpretation of certain fields may vary based on the blockchain. Chain-specific explanations are provided in the sections that follow. | Field | Description | | -------------------------- | ------------------------------------------------------------------------------------- | | `transactionId` | The unique identifier of the transaction. | | `transactionStatus` | The [success status](#transaction-success-status) of the transaction. | | `standardPaymentReference` | A reference defined in the [standard payment reference](#standard-payment-reference). | | `oneToOne` | Indicates if the transaction involves a single sender and a single receiver. | | `sourceAddress` | The originating address involved in the transaction. | | `sourceAddressesRoot` | The root of the Merkle tree of the source addresses. | | `spentAmount` | The total amount spent by the source address. | | `intendedSourceAmount` | The expected amount intended to be sent from the source address. | | `receivingAddress` | The target address receiving the payment. | | `intendedReceivingAddress` | The expected target address intended to receive the payment. | | `receivedAmount` | The actual amount received by the receiving address. | | `intendedReceivingAmount` | The expected amount intended to be received. | :::note - **Standard Address Hashes**: [Standard address hashes](#standard-address-hash) can be derived from addresses. - If `transactionStatus` is not `SUCCESS`, the `receivingAddress` is set to an empty string, and its hash defaults to a zeroed 32-byte string. - **Standard Addresses Root** is the root of the Merkle tree build on double keccak256 hashes of the all source addresses of the transaction. ::: ### UTXO chains (Bitcoin and Dogecoin) The payment summary for Bitcoin and Dogecoin is derived using specified indices for a transaction input and output. #### Conditions - If the specified input or output does not exist, or lacks an address (e.g., outputs using `OP_RETURN`), no summary is generated. - Coinbase transactions are not summarized. - If a transaction has additional outputs with the same address as output, the request is rejected. #### Data Sources - For Bitcoin, all transaction details are retrieved using the `getrawtransaction` endpoint (verbosity 2) and `getblock`. This requires a Bitcoin node version ≥ 25.0. - For Dogecoin, since `getrawtransaction` with verbosity 2 is not supported, alternative methods must be used to access input transaction data. | Field | Description | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `transactionId` | The transaction ID (`txid`). For SegWit transactions, this differs from `hash`. | | `oneToOne` | `true` if only `sourceAddress` is present in inputs, and outputs include only `receivingAddress`, `sourceAddress` (for change), or `OP_RETURN`. | | `sourceAddress` | Address of the specified input. | | `spentAmount` | Total value of all inputs with `sourceAddress` minus total value of all outputs to `sourceAddress`. | | `intendedSourceAmount` | Same as `spentAmount`. | | `receivingAddress` | Address of the specified output. | | `intendedReceivingAddress` | Always matches `receivingAddress`. | | `receivedAmount` | Total value of outputs to `receivingAddress` minus total value of inputs from `receivingAddress`. | | `intendedReceivingAmount` | Same as `receivedAmount`. | ### Account-based chains (XRPL) The payment summary on XRPL is applicable only for transactions of type `Payment`. #### Conditions - Only `Payment` transactions are summarized; other transaction types are ignored. - A successful payment has exactly one sender and at most one receiver. If unsuccessful, no receiver is recorded. #### Data Sources - Transaction details are obtained via the [`tx`](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx) method. - Changes made by the transaction are recorded in the `meta` field (or `metaData` if fetched via the `ledger` method) under `AffectedNodes`. Balance changes are found within `ModifiedNodes`, by comparing `FinalFields` and `PreviousFields`. | Field | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `transactionId` | Transaction hash found in the `hash` field. | | `oneToOne` | Always `true`, as each `Payment` transaction has exactly one sender and at most one receiver. | | `sourceAddress` | Address that sent the payment, reducing its balance. | | `spentAmount` | Amount by which `sourceAddress`'s balance was reduced. | | `intendedSourceAmount` | Calculated as `Amount + Fee`. If `transactionStatus` is `SUCCESS`, it matches `spentAmount`. | | `receivingAddress` | Address that received the payment. If unsuccessful, this is an empty string. | | `intendedReceivingAddress` | Address specified in the `Destination` field. | | `receivedAmount` | Amount by which the `receivingAddress`'s balance was increased. Can be zero if the transaction failed. | | `intendedReceivingAmount` | Expected increase in `intendedReceivingAddress`'s balance if successful. Found in the `Amount` field. Matches `spentAmount` if successful. | ## Standard payment reference A standard payment reference is defined as a 32-byte sequence that can be added to a payment transaction, in the same way that a payment reference is attached to a traditional banking transaction. ### Bitcoin and Dogecoin - Uses `OP_RETURN` to store references. - A transaction is considered to have a `standardPaymentReference` defined if it has: - Exactly one output UTXO with `OP_RETURN` script, and - The script is of the form `OP_RETURN ` or `6a `in hex, where the length of the reference is 32 bytes. - Then `0x` is the `standardPaymentReference`. ### XRPL - Uses the `memoData` field. - A transaction has a `standardPaymentReference` if it has: - Exactly one [Memo](https://xrpl.org/transaction-common-fields.html#memos-field), and - The `memoData` of this field is a hex string that represents a byte sequence of exactly 32 bytes. - This 32-byte sequence defines the `standardPaymentReference`. ## Transaction success status Transactions on different blockchains have various success statuses. Some blockchains may include transactions even if they failed to execute as intended. | Status | Code | | ------------------ | ---- | | `SUCCESS` | 0 | | `SENDER_FAILURE` | 1 | | `RECEIVER_FAILURE` | 2 | **Bitcoin and Dogecoin** It is not possible to include an unsuccessful transaction in a Bitcoin or Dogecoin block. Hence, if a transaction is included on a confirmed block, its status is "SUCCESS." **XRPL** On XRPL, some transactions that failed (based on the reason for failure) can be included in a confirmed block. - **`tesSUCCESS`**: Transaction successful. - **`tec`-class codes**: Indicate reasons for failure, such as: - `tecDST_TAG_NEEDED`: Missing required destination tag. - `tecNO_DST`: Nonexistent or unfunded destination address. - `tecNO_PERMISSION`: Source address lacks permission to send funds. ## Standard address hash The **standard address hash** is defined as the `keccak256` hash of the standard address as a string: ```solidity keccak256(standardAddress) ``` :::note[standard address] If an address is case insensitive, the standard address is lowercase. If an address is case sensitive, there is always only one (correct) form of the address. ::: **Examples:** | Chain | Standard Address | Standard Address Hash | | ------------------ | -------------------------------------------- | -------------------------------------------------------------------- | | Bitcoin (`Base58`) | `1FWQiwK27EnGXb6BiBMRLJvunJQZZPMcGd` | `0x8f651b6990a4754c58fcb5c5a11f4d40f8ddfdeb0e4f67cdd06c27f8d7bcbe33` | | Bitcoin (`Bech32`) | `bc1qrmvxmwgqfr5q4fvtvnxczwxwm966n53c4lxh4v` | `0xf75dc4b039ac72e037d67199bb92fa25db32b2210954df99637428473d47cedf` | | Dogecoin | `DL2H9FuaXsxivSs1sRtuJ8uryosyAj62XX` | `0x51064c88c6b8e9d58b2abeae37a773bf89c9b279f8a05fa0ac0e81ebe13d2f4f` | | XRPL | `rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv` | `0xa491aed10a1920ca31a85ff29e4bc410705d37d4dc9e690d4d500bcedfd8078f` | ## Finality Blockchains have varying confirmation depths to consider blocks as final. | Chain | `chainId` | Confirmations required | Confirmation time | | -------- | --------- | ---------------------- | ----------------- | | Bitcoin | 0 | 6 | ≈60 mins | | Dogecoin | 2 | 60 | ≈60 mins | | XRPL | 3 | 3 | ≈12 seconds | ## Contract Interface For the complete interface definition, see [`IPayment`](/fdc/reference/IPayment.mdx). --- ## ReferencedPaymentNonexistence Assertion that a specific payment, agreed upon to be completed by a certain deadline, has **not been made**. If confirmed, it shows that no transaction meeting the specified criteria (address, amount, reference) was found within the given block range. This Information can be used, for example, to justify the liquidation of funds locked in a smart contract on Songbird if a payment is missed. ## Supported chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet v3), `testDOGE`, `testXRP` | ## Request | Field | Solidity Type | Description | | -------------------------- | ------------- | ----------------------------------------------------------------------------- | | `minimalBlockNumber` | `uint64` | The block number to start the search range. | | `deadlineBlockNumber` | `uint64` | The block number to include as the end of the search range. | | `deadlineTimestamp` | `uint64` | The timestamp to include as the end of the search range. | | `destinationAddressHash` | `bytes32` | The standard hash of the address where the payment was expected. | | `amount` | `uint256` | The required payment amount in minimal units. | | `standardPaymentReference` | `bytes32` | The standard payment reference associated with the payment. Must not be zero. | | `checkSourceAddresses` | `bool` | If true, the source addresses root is checked. | | `sourceAddressesRoot` | `bytes32` | The root of the Merkle tree of the source addresses. | :::note **Standard Addresses Root** is the root of the Merkle tree build on double keccak256 hashes of the all source addresses of the transaction. ::: ## Response | Field | Solidity Type | Description | | ----------------------------- | ------------- | ------------------------------------------------------------- | | `minimalBlockTimestamp` | `uint64` | The timestamp of the block at `minimalBlockNumber`. | | `firstOverflowBlockNumber` | `uint64` | The block number immediately after the `deadlineBlockNumber`. | | `firstOverflowBlockTimestamp` | `uint64` | The timestamp of the `firstOverflowBlockNumber`. | - **`firstOverflowBlockNumber`**: This is the first block with a height greater than `deadlineBlockNumber` and a timestamp later than `deadlineTimestamp`. - The search range includes blocks from `minimalBlockNumber` (inclusive) to `firstOverflowBlockNumber` (exclusive). ## Verification process 1. **Block Confirmation**: - If the `firstOverflowBlock` cannot be determined or lacks the required [number of confirmations](#finality), the request is rejected. - The request is also rejected if `firstOverflowBlockNumber` is less than or equal to `minimalBlockNumber`. 2. **Search Range**: - The search range includes blocks from `minimalBlockNumber` to `firstOverflowBlockNumber` (exclusive). - If the verifier does not have complete visibility of all blocks in this range, the request is rejected. 3. **Transaction Validation**: - The request is confirmed if **no transaction** meeting the specified criteria (address, source addresses root, amount, reference) is found within the specified block range. - The criteria and timestamp interpretation are specific to each chain. The verification process is chain-specific, with details described below. ### UTXO chains (Bitcoin and Dogecoin) #### Transaction Criteria - The transaction **must not be a coinbase transaction**. - The transaction must include the specified [standard payment reference](#standard-payment-reference). - If `checkSourceAddresses` is set to true, the `sourceAddressesRoot` of the transaction must match the specified `sourceAddressesRoot`. - The sum of all output values sent to the specified address **minus** the sum of all input values from the same address must be **greater or equal to the specified `amount`**. - Typically, the sum of input values for the specified address is zero. #### Timestamp - Uses the **mediantime** of the block. ### Account-based chains (XRPL) #### Transaction Criteria - The transaction must be of type **Payment**. - The transaction must include the specified [standard payment reference](#standard-payment-reference). - If `checkSourceAddresses` is set to true, the `sourceAddressesRoot` of the transaction must match the specified `sourceAddressesRoot`. - One of the following conditions must hold: - The transaction status is `SUCCESS` and the amount received by the specified address is greater or equal to the specified `amount`. - The transaction status is `RECEIVER_FAILURE` and the specified address would have received an amount greater or equal to the specified `amount` if the transaction had succeeded. #### Timestamp - Uses the **close_time** of the ledger, converted to UNIX time. :::note[Lowest used timestamp] For the `lowestUsedTimestamp` parameter, the value of `minimalBlockTimestamp` is used. ::: ## Standard payment reference A standard payment reference is defined as a 32-byte sequence that can be added to a payment transaction, in the same way that a payment reference is attached to a traditional banking transaction. ### Bitcoin and Dogecoin - Uses `OP_RETURN` to store references. - A transaction is considered to have a `standardPaymentReference` defined if it has: - Exactly one output UTXO with `OP_RETURN` script, and - The script is of the form `OP_RETURN ` or `6a `in hex, where the length of the reference is 32 bytes. - Then `0x` is the `standardPaymentReference`. ### XRPL - Uses the `memoData` field. - A transaction has a `standardPaymentReference` if it has: - Exactly one [Memo](https://xrpl.org/transaction-common-fields.html#memos-field), and - The `memoData` of this field is a hex string that represents a byte sequence of exactly 32 bytes. - This 32-byte sequence defines the `standardPaymentReference`. ## Finality Blockchains have varying confirmation depths to consider blocks as final. | Chain | `chainId` | Confirmations required | Confirmation time | | -------- | --------- | ---------------------- | ----------------- | | Bitcoin | 0 | 6 | ≈60 mins | | Dogecoin | 2 | 60 | ≈60 mins | | XRPL | 3 | 3 | ≈12 seconds | ## Contract Interface For the complete interface definition, see [`IReferencedPaymentNonexistence`](/fdc/reference/IReferencedPaymentNonexistence.mdx). --- ## Web2Json An attestation type that fetches JSON data from the given URL, applies a jq filter to transform the returned result, and finally returns the structured data as ABI encoded data. **Supported sources:** WEB2 ## Request body | Field | Solidity type | Description | | --------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `url` | `string` | URL of the data source. | | `httpMethod` | `string` | HTTP method to be used for the request. Supported methods: GET, POST, PUT, PATCH, DELETE. | | `headers` | `string` | Stringified key-value pairs representing headers to be used in the request. Use `{}` if no headers are required. | | `queryParams` | `string` | Stringified key-value pairs representing query parameters to be appended to the URL of the request. Use `{}` if no query parameters are required. | | `body` | `string` | Stringified key-value pairs representing the body to be used in the request. Use `{}` if the body is not required. | | `postProcessJq` | `string` | jq filter used to post-process the JSON response from the URL. | | `abiSignature` | `string` | ABI signature of the struct used to encode the data after jq post-processing. | ## Response body | Field | Solidity type | Description | | ---------------- | ------------- | ---------------------------------------------------------------- | | `abiEncodedData` | `bytes` | Raw binary data encoded to match the function parameters in ABI. | ## Lowest Used Timestamp For `lowestUsedTimestamp`, `0xffffffffffffffff` ($2^{64}-1$ in hex) is used. ## Verification The JSON response is queried from the provided URL source using the specified HTTP method, headers, query parameters, and body. If the query is unsuccessful (e.g., attempts to access a disallowed IP or hostname, attempts to redirect, takes longer than 1 second, returns a JSON response with more than 5000 keys or a depth greater than 10, returns a content-type other than "application/json", or does not return a valid JSON response), the request is rejected. The provided jq filter is applied to the valid JSON response. If the jq filter exceeds 5000 characters, if jq filtering fails, or if jq filtering takes longer than 500 milliseconds, the request is rejected. The provided ABI signature is used to encode the jq-filtered JSON data. If encoding fails, the request is rejected. `LowestUsedTimestamp` is unlimited. ## Whitelisted URLs Before a request to the FDC for data at an endpoint can be made, the specific URL must be whitelisted. On mainnets, this is done through government proposals (details in the [FIP.14](https://proposals.flare.network/FIP/FIP_14.html#31-process-of-adding-updating-or-removing-an-api-endpoint) proposal). On testnets whitelisting is **not required**, any endpoint can be used by selecting the `PublicWeb2` source. --- ## XRPPaymentNonexistence Assertion that **no** XRP Ledger `Payment` transaction matching the specified destination, amount, memo, and/or destination tag was confirmed in the given block range. `XRPPaymentNonexistence` differs from the chain-agnostic [`ReferencedPaymentNonexistence`](/fdc/attestation-types/referenced-payment-nonexistence.mdx) in how the payment is matched: instead of a [standard payment reference](/fdc/attestation-types/referenced-payment-nonexistence.mdx#standard-payment-reference), matches use the **hash of the first Memo's `MemoData`** and/or the **`DestinationTag`** — the two fields XRPL applications natively use to correlate payments. This makes it suited for invoicing flows where payers identify themselves via a destination tag, as well as for memo-based protocols. ## Supported chains | Network Type | Supported Chains | | ------------ | ------------------------ | | **Mainnet** | `XRP` (XRP Ledger) | | **Testnet** | `testXRP` (XRPL Testnet) | ## Request | Field | Solidity Type | Description | | ------------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `minimalBlockNumber` | `uint64` | The starting ledger of the search range (inclusive). | | `deadlineBlockNumber` | `uint64` | The ledger to be included as the end of the search range. | | `deadlineTimestamp` | `uint64` | The timestamp to be included as the end of the search range. | | `destinationAddressHash` | `bytes32` | The [standard address hash](/fdc/attestation-types/payment.mdx#standard-address-hash) of the expected receiver. | | `amount` | `uint256` | The required payment amount in drops. | | `checkFirstMemoData` | `bool` | If `true`, the first Memo's `MemoData` hash must match `firstMemoDataHash` for a transaction to be considered a match. | | `firstMemoDataHash` | `bytes32` | The [standard hash](/fdc/attestation-types/payment.mdx#standard-address-hash) of the expected first Memo's `MemoData`. Considered only when `checkFirstMemoData` is `true`. | | `checkDestinationTag` | `bool` | If `true`, the transaction's `DestinationTag` must equal `destinationTag` for a transaction to be considered a match. | | `destinationTag` | `uint256` | The expected destination tag. Considered only when `checkDestinationTag` is `true`. XRPL only supports `uint32` values. | | `proofOwner` | `address` | Address authorized to use the proof, where applicable. The verifier lower-cases this value. | :::warning[At least one match field is required] At least one of `checkFirstMemoData` or `checkDestinationTag` must be `true`. If both are `false`, the request would match any sufficiently-sized payment to the destination, which is rarely intended. ::: ## Response | Field | Solidity Type | Description | | ----------------------------- | ------------- | --------------------------------------------------------------------------------------------------- | | `minimalBlockTimestamp` | `uint64` | The timestamp of the ledger at `minimalBlockNumber`. | | `firstOverflowBlockNumber` | `uint64` | The first ledger with both `blockNumber > deadlineBlockNumber` and `timestamp > deadlineTimestamp`. | | `firstOverflowBlockTimestamp` | `uint64` | The timestamp of `firstOverflowBlockNumber`. | The search range covers ledgers from `minimalBlockNumber` (inclusive) to `firstOverflowBlockNumber` (exclusive). ## Verification process 1. **Range validation**: If `minimalBlockNumber` or `deadlineBlockNumber` is negative or unreasonably large, the request is rejected. If `firstOverflowBlock` cannot be determined or lacks the required [confirmations](#finality), the request is rejected. If `minimalBlockNumber >= firstOverflowBlockNumber`, the request is rejected. 2. **Visibility**: If the verifier does not have a complete view of the search range, the request is rejected. 3. **Match scan**: A transaction is considered a **match** (and would invalidate the nonexistence claim) when **all** of the following hold: - The transaction is of type `Payment` and has a successful payment summary with one sender and one receiver. - The receiver's [standard address hash](/fdc/attestation-types/payment.mdx#standard-address-hash) equals `destinationAddressHash`. - The intended receiving amount is **greater than or equal to** `amount`. - The transaction did not fail with `SENDER_FAILURE` (so `SUCCESS` and `RECEIVER_FAILURE` outcomes both qualify, mirroring [`ReferencedPaymentNonexistence`](/fdc/attestation-types/referenced-payment-nonexistence.mdx#account-based-chains-xrpl) on XRPL). - If `checkFirstMemoData` is `true`: the standard hash of the first Memo's `MemoData` (or the standard hash of an empty value when no memo is present) equals `firstMemoDataHash`. - If `checkDestinationTag` is `true`: the transaction has a destination tag, and it equals `destinationTag`. 4. **Confirmation**: The request is confirmed if **no** transaction in the search range matches all applicable criteria. :::note[Lowest used timestamp] For the `lowestUsedTimestamp` parameter, the value of `minimalBlockTimestamp` is used. ::: ## Finality XRPL ledgers must reach the required confirmation depth before the search range can be considered complete. | Chain | `chainId` | Confirmations required | Confirmation time | | ----- | --------- | ---------------------- | ----------------- | | XRPL | 3 | 3 | ≈12 seconds | ## Contract Interface For the complete interface definition, see [`IXRPPaymentNonexistence`](/fdc/reference/IXRPPaymentNonexistence.mdx). --- ## XRPPayment Information about a `Payment` transaction on the **XRP Ledger** transferring native XRP from a source address to a receiving address. Unlike the chain-agnostic [`Payment`](/fdc/attestation-types/payment.mdx) attestation type, `XRPPayment` exposes XRPL-specific fields directly in the response: the source `r`-address (XRPL addresses) as a string, raw bytes of the first Memo's `MemoData`, and the destination tag. This avoids the need for off-chain helpers when a consumer contract needs to act on these fields. ## Supported chains | Network Type | Supported Chains | | ------------ | ------------------------ | | **Mainnet** | `XRP` (XRP Ledger) | | **Testnet** | `testXRP` (XRPL Testnet) | ## Request | Field | Solidity Type | Description | | --------------- | ------------- | ------------------------------------------------------------------------------------------- | | `transactionId` | `bytes32` | ID of the payment transaction. | | `proofOwner` | `address` | Address authorized to use the proof, where applicable. The verifier lower-cases this value. | ## Response | Field | Solidity Type | Description | | ------------------------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `blockNumber` | `uint64` | Number of the ledger in which the transaction is included. | | `blockTimestamp` | `uint64` | The timestamp of the ledger, derived from `close_time` converted to UNIX time. | | `sourceAddress` | `string` | The XRPL `r`-address of the source. | | `sourceAddressHash` | `bytes32` | [Standard address hash](#standard-address-hash) of the source address. | | `receivingAddressHash` | `bytes32` | Standard address hash of the receiving address. Zero `bytes32` if `status` is not success. | | `intendedReceivingAddressHash` | `bytes32` | Standard address hash of the intended receiving address (the `Destination` field). Relevant when the transaction failed. | | `spentAmount` | `int256` | Amount in drops actually spent by the source address. | | `intendedSpentAmount` | `int256` | Amount in drops the source intended to spend (`Amount + Fee`). Relevant when the transaction failed. | | `receivedAmount` | `int256` | Amount in drops received by the receiving address. | | `intendedReceivedAmount` | `int256` | Amount in drops the receiving address would have received had the transaction succeeded. | | `hasMemoData` | `bool` | `true` if the transaction has a `MemoData` field. | | `firstMemoData` | `bytes` | Raw bytes of the `MemoData` field of the **first** Memo. Empty when `hasMemoData` is false. | | `hasDestinationTag` | `bool` | `true` if the transaction has a destination tag. | | `destinationTag` | `uint256` | Destination tag of the transaction. `0` when `hasDestinationTag` is false. XRPL currently only supports `uint32` destination tags. | | `status` | `uint8` | [Transaction success status](#transaction-success-status). | :::note[Differences from `Payment`] `XRPPayment` is XRPL only and replaces the `inUtxo` / `utxo` request fields with `proofOwner`. The response drops `standardPaymentReference`, `oneToOne`, and `sourceAddressesRoot` (always implied for XRPL Payment transactions) and adds `sourceAddress`, `hasMemoData`, `firstMemoData`, `hasDestinationTag`, and `destinationTag`. ::: ## Verification process 1. The transaction identified by `transactionId` is fetched from an XRPL node or indexer via the [`tx`](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx) method. 2. If the transaction cannot be retrieved, or its ledger lacks the required [confirmations](#finality), the request is rejected. 3. A [payment summary](/fdc/attestation-types/payment.mdx#payment-summary) is computed for the transaction. - Only XRPL transactions of type `Payment` produce a summary; other types are rejected. - A successful payment has exactly one sender and at most one receiver. Multi-output payments are rejected. 4. `blockNumber` and `blockTimestamp` are extracted from the ledger if not present in the transaction data. `blockTimestamp` is the ledger `close_time` converted to UNIX time. 5. The first Memo's `MemoData` (if any) and the `DestinationTag` (if any) are surfaced unchanged in the response. :::note[Lowest used timestamp] For the `lowestUsedTimestamp` parameter, the `blockTimestamp` of the transaction is used. ::: ## Transaction success status Transactions on different blockchains have various success statuses. Some blockchains may include transactions even if they failed to execute as intended. | Status | Code | | ------------------ | ---- | | `SUCCESS` | 0 | | `SENDER_FAILURE` | 1 | | `RECEIVER_FAILURE` | 2 | **Bitcoin and Dogecoin** It is not possible to include an unsuccessful transaction in a Bitcoin or Dogecoin block. Hence, if a transaction is included on a confirmed block, its status is "SUCCESS." **XRPL** On XRPL, some transactions that failed (based on the reason for failure) can be included in a confirmed block. - **`tesSUCCESS`**: Transaction successful. - **`tec`-class codes**: Indicate reasons for failure, such as: - `tecDST_TAG_NEEDED`: Missing required destination tag. - `tecNO_DST`: Nonexistent or unfunded destination address. - `tecNO_PERMISSION`: Source address lacks permission to send funds. ## Standard address hash The **standard address hash** is the `keccak256` hash of the XRPL standard address as a string: ```solidity keccak256(standardAddress) ``` XRPL `r`-addresses are case-sensitive, so the address has a single canonical form (no lower-casing applied). **Example:** | Chain | Standard Address | Standard Address Hash | | ----- | ------------------------------------ | -------------------------------------------------------------------- | | XRPL | `rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv` | `0xa491aed10a1920ca31a85ff29e4bc410705d37d4dc9e690d4d500bcedfd8078f` | ## Finality XRPL ledgers must reach the required confirmation depth before the transaction can be proven. | Chain | `chainId` | Confirmations required | Confirmation time | | ----- | --------- | ---------------------- | ----------------- | | XRPL | 3 | 3 | ≈12 seconds | ## Contract Interface For the complete interface definition, see [`IXRPPayment`](/fdc/reference/IXRPPayment.mdx). --- ## FDC by hand In this guide, we will demonstrate how [FDC workflow](https://dev.flare.network/fdc/overview.md) can be performed almost entirely without the use of any script. The purpose of this guide is to familiarize the reader with the FDC workflow and introduce them to tooling surrounding FDC. This is **not** how the FDC is intended to be used. Nonetheless, the tools described in this guide may prove useful when debugging the actual code. ## Request data In this guide, we will check whether the string `rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe` is a valid XRPL testnet address. This is an example of the `AddressValidity` attestation request. We chose it for demonstration purposes because it requires a single input parameter, but the same process would work for any of the FDC [attestation types](https://dev.flare.network/fdc/attestation-types.md). ## Preparing the request The FDC accepts requests as byte strings, encoded in a special way. These can be produced manually, but an easier way to do it is through a verifier server. A verifier server checks the validity of input data and returns an `abiEncodedRequest` that can be passed on to the FDC. In this guide, we will use the Flare-hosted verifier server. Its interactive documentation is available at [the address](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc#/). The interface requires authentication; the key `00000000-0000-0000-0000-000000000000` can be used. To authenticate, click `Authorize` in the top right corner, and input the above key. Once authenticated, scroll down to the **AddressValidity** section, and expand the `/verifier/xrp/AddressValidity/prepareRequest` item. Click on the `Try it out` button in the upper right corner of the expanded card, and a `Request body` field will appear. Replace the `addressStr` field value with the above address `rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe`, and ensure that the form matches: ```json title="Request body" { "attestationType": "0x4164647265737356616c69646974790000000000000000000000000000000000", "sourceId": "0x7465737458525000000000000000000000000000000000000000000000000000", "requestBody": { "addressStr": "r3wvdzNDkNJ3e5ut1RJfWtBxDHT9sddQRQ" } } ``` Then click `Execute`. The `abiEncodedRequest` will appear under the `Response body` section below, along with the request status which should be `VALID`. {" "} ```json title="Response body" { "status": "VALID", "abiEncodedRequest": "0x4164647265737356616c696469747900000000000000000000000000000000007465737458525000000000000000000000000000000000000000000000000000cf6de269807c522f39f90e9a84ec13993f58d5473fe918acbe6197efb5a17c2e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002272337776647a4e446b4e4a336535757431524a665774427844485439736464515251000000000000000000000000000000000000000000000000000000000000" } ``` There are two additional values within the request body that we have not yet properly explained. They are the UTF8 hex-encoded strings of `AddressValidity` and `testXRP` respectively, zero-padded to 32 bytes. These will be different for each attestation type and source. The full list of values is available below.
Interactive documentation for all attestation types, and the corresponding values for the `attestationType` and `sourceId` fields. [AddressValidity](https://dev.flare.network/fdc/attestation-types/address-validity.md) [testBTC](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/) [testXRP](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc#/) [testDOGE](https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc#/) [BTC](https://fdc-verifiers-mainnet.flare.network/verifier/btc/api-doc#/) [XRP](https://fdc-verifiers-mainnet.flare.network/verifier/xrp/api-doc#/) [DOGE](https://fdc-verifiers-mainnet.flare.network/verifier/doge/api-doc#/) [EVMTransaction](https://dev.flare.network/fdc/attestation-types/evm-transaction.md)
Input the appropriate value into the encoder bellow. ## Submitting the request We submit the attestation request to the FDC through the `FdcHub` contract. It is one of the official Flare contracts, and it is deployed on each of the Flare chains. We will submit the attestation request to the `FdcHub` contract on Coston2. We can use [Flare Explorer](https://coston2-explorer.flare.network/address/0x48aC463d7975828989331F4De43341627b9c5f1D). First, we must navigate to the `Contract` tab on the explorer, and then to [Write contract](https://coston2-explorer.flare.network/address/0x48aC463d7975828989331F4De43341627b9c5f1D?tab=write_contract). We can then open the `requestAttestation` function widget. Here we input the `abiEncodedRequest` value that we received from the verifier server, as well as the fee that needs to be paid to the contract. The request fee can be queried from the `FdcRequestFeeConfigurations` contract, through its [`getRequestFee` function](https://coston2-explorer.flare.network/address/0x191a1282Ac700edE65c5B0AaF313BAcC3eA7fC7e?tab=read_contract). The input parameter for this function is the `abiEncodedRequest` from the verifier server. To execute the `requestAttestation` call, we need to connect the wallet. We input the `abiEncodedRequest` value, and the amount returned by the `getRequestFee` function. After the transaction has been executed successfully, we need to open it up in the explorer. We should remember its block number for later.
Links to `FdcHub` and `FdcRequestFeeConfigurations` contracts on all Flare chains. ### FdcHub Coston [Flare Explorer](https://coston-explorer.flare.network/address/0x1c78A073E3BD2aCa4cc327d55FB0cD4f0549B55b) Coston2 [Flare Explorer](https://coston2-explorer.flare.network/address/0x48aC463d7975828989331F4De43341627b9c5f1D) Songbird [Flare Explorer](https://songbird-explorer.flare.network/address/0xCfD4669a505A70c2cE85db8A1c1d14BcDE5a1a06) Flare [Flare Explorer](https://flare-explorer.flare.network/address/0xc25c749DC27Efb1864Cb3DADa8845B7687eB2d44) ### FdcRequestFeeConfigurations Coston [Flare Explorer](https://coston-explorer.flare.network/address/0x2bBfb46aC3A71A6725699004B8a8fE4C928E7108) Coston2 [Flare Explorer](https://coston2-explorer.flare.network/address/0x191a1282Ac700edE65c5B0AaF313BAcC3eA7fC7e) Songbird [Flare Explorer](https://songbird-explorer.flare.network/address/0x8998a3b85350aA4CA5f55cD80ab1f7C9C0ddf02C) Flare [Flare Explorer](https://flare-explorer.flare.network/address/0x259852Ae6d5085bDc0650D3887825f7b76F0c4fe)
## Waiting for the voting round to finalize Before we can retrieve the proof and data, we must wait for the voting round in which the request was made to be finalized. To check whether the round has been finalized, we need to know the block in which the attestation request transaction was made; or more precisely, we need the timestamp of that block. We can calculate the voting round ID from the transaction timestamp using the following formula. $$ \text{votingRoundId} = \frac{\text{transactionTimestamp} - \text{firsVotingRoundStartTs}}{\text{votingEpochDurationSeconds}} $$ Here, the `transactionTimestamp` represents the timestamp of the transaction in which we submitted the attestation request. The other two values are: $$ \text{firsVotingRoundStartTs} = 1658430000 \qquad \text{votingEpochDurationSeconds} = 90 $$ The following form calculates the round ID for you. Once we know the voting round ID, we can go to the `Finalizations` tab of the [Coston2 Systems Explorer](https://coston2-systems-explorer.flare.rocks/finalizations). When the round has been finalized, its ID will appear on the list.
Links to all System Explorers [Coston](https://coston-systems-explorer.flare.rocks/finalizations) [Coston2](https://coston2-systems-explorer.flare.rocks/finalizations) [Songbird](https://songbird-systems-explorer.flare.rocks/finalizations) [Flare](https://flare-systems-explorer.flare.rocks/finalizations)
## Preparing the proof request Once the round has been finalized, we can prepare the proof request using the [Flare Data Availability Client](https://ctn2-data-availability.flare.network/api-doc#/) interactive documentation. As in the previous step, we first need to authenticate using the same API key, `00000000-0000-0000-0000-000000000000`. We can then expand the `/api/v1/fdc/proof-by-request-round` widget, and click `Try it out`. We input the voting round ID of our request under the `votingRoundId` field, and the `abiEncodedRequest` under the `requestBytes` field. Then we click on the `Execute` button. The proof response will appear under the `Response body` section. ```json title="Response body" { "response": { "attestationType": "0x4164647265737356616c69646974790000000000000000000000000000000000", "sourceId": "0x7465737458525000000000000000000000000000000000000000000000000000", "votingRound": 1028678, "lowestUsedTimestamp": 18446744073709552000, "requestBody": { "addressStr": "r3wvdzNDkNJ3e5ut1RJfWtBxDHT9sddQRQ" }, "responseBody": { "isValid": true, "standardAddress": "r3wvdzNDkNJ3e5ut1RJfWtBxDHT9sddQRQ", "standardAddressHash": "0x1e2adcb99103f6396903f33db1526fa66aedfbfee4405def0ef69e0fcd949f47" } }, "proof": [ "0x5422093333fabcdd137138efa611efcbb932e65184b12415d2f74d511c1f27b9", "0x28b15d5e07d2249c7acd8b6deae1706e01be84231264bc66d99553970dbf3bde", "0x486452aac82578f8b2b9a88df400e80a33d9945111d503f2b205d4209279f225", "0xc7188d9db7d522ba2b9a84d7aec34745e6acde1a5118372b53aa072d1ad61894" ] } ``` The Data Availability Client informs us that the address is, indeed, a valid XRPL address. But the above response is not entirely correct; the timestamp is off by `385`. This is a JavaScript rounding error. To get the right response, we need to use the `curl` command in a terminal. The `curl` command is displayed as the first part of the `Responses` section. In this case, it is: ```sh curl -X 'POST' \ 'https://ctn2-data-availability.flare.network/api/v1/fdc/proof-by-request-round' \ -H 'accept: application/json' \ -H 'x-api-key: 00000000-0000-0000-0000-000000000000' \ -H 'Content-Type: application/json' \ -H 'X-CSRFTOKEN: rYJ04UxDuekdzYgChGXRIfQI904VD8qK2UtiBwZ1uMLlkAEwPgNrxvCLsNS5PdYX' \ -d '{ "votingRoundId": 1028678, "requestBytes": "0x4164647265737356616c696469747900000000000000000000000000000000007465737458525000000000000000000000000000000000000000000000000000cf6de269807c522f39f90e9a84ec13993f58d5473fe918acbe6197efb5a17c2e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002272337776647a4e446b4e4a336535757431524a665774427844485439736464515251000000000000000000000000000000000000000000000000000000000000" }' ``` The output of the `curl` command is then: ```json { "response": { "attestationType": "0x4164647265737356616c69646974790000000000000000000000000000000000", "sourceId": "0x7465737458525000000000000000000000000000000000000000000000000000", "votingRound": 1028678, "lowestUsedTimestamp": 18446744073709551615, "requestBody": { "addressStr": "r3wvdzNDkNJ3e5ut1RJfWtBxDHT9sddQRQ" }, "responseBody": { "isValid": true, "standardAddress": "r3wvdzNDkNJ3e5ut1RJfWtBxDHT9sddQRQ", "standardAddressHash": "0x1e2adcb99103f6396903f33db1526fa66aedfbfee4405def0ef69e0fcd949f47" } }, "proof": [ "0x5422093333fabcdd137138efa611efcbb932e65184b12415d2f74d511c1f27b9", "0x28b15d5e07d2249c7acd8b6deae1706e01be84231264bc66d99553970dbf3bde", "0x486452aac82578f8b2b9a88df400e80a33d9945111d503f2b205d4209279f225", "0xc7188d9db7d522ba2b9a84d7aec34745e6acde1a5118372b53aa072d1ad61894" ] } ```
Links to all Data Availability Clients [Coston](https://ctn-data-availability.flare.network/api-doc#/) [Coston2](https://ctn2-data-availability.flare.network/api-doc#/) [Songbird](https://sgb-data-availability.flare.network/api-doc#/) [Flare](https://flr-data-availability.flare.network/api-doc#/)
## Verifying the data The last thing remaining is to verify the proof returned by the Data Availability Client. We do so through the [`FdcVerification` contract](https://coston2-explorer.flare.network/address/0x075bf301fF07C4920e5261f93a0609640F53487D?tab=read_write_contract). We input the above `Response body` data as parameters to the `verifyAddressValidity` function of the `FdcVerification` contract. When we `Read` the function, we get back `true`, which means that the proof is valid.
Links to `FdcVerification` contract on all Flare chains. Coston [Flare Explorer](https://coston-explorer.flare.network/address/0x57a2db68fb40f6C61342FF4beF283AE185eA8E51) Coston2 [Flare Explorer](https://coston2-explorer.flare.network/address/0x075bf301fF07C4920e5261f93a0609640F53487D) Songbird [Flare Explorer](https://songbird-explorer.flare.network/address/0xd283afC5A67E2d4Bc700b5B640328Bda22450621) Flare [Flare Explorer](https://flare-explorer.flare.network/address/0x9394c7A36b3Da8de1b4F27cdD0a554dA4Fa7132d)
--- ## AddressValidity(Foundry) The [AddressValidity](/fdc/attestation-types/address-validity) attestation type validates whether a string represents a valid address on supported blockchain networks (`BTC`, `DOGE`, and `XRP`). This validation ensures addresses meet chain-specific formatting and checksum requirements before they're used in transactions or smart contracts. The full specification is available on the official [specification repo](/fdc/attestation-types/address-validity). The primary contract interface for this attestation type is [`IAddressValidity`](/fdc/reference/IFdcHub). Let's walk through validating a Bitcoin testnet address using the FDC protocol. We will use the address `mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs` as an example throughout this guide. You can swap this with any valid testnet address from the supported chains. You can follow this tutorial with any other valid address - just make sure it is a valid testnet address. This validation process works identically for `BTC`, `DOGE`, and `XRP` addresses, with only minor chain-specific parameter adjustments which we'll highlight throughout the guide. In this guide, we will follow the steps outlined in the [FDC overview](/fdc/overview). Our implementation requires handling the FDC voting round finalization process. To manage this, we will create separate scripts in `script/fdcExample/AddressValidity.s.sol` that handle different stages of the validation process: ```solidity title="script/fdcExample/AddressValidity.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... // Configuration constants string constant attestationTypeName = "AddressValidity"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { ... } contract SubmitAttestationRequest is Script { ... } contract RetrieveDataAndProof is Script { ... } contract Deploy is Script { ... } ... ``` The names of included contracts mostly mirror the steps described in the [FDC overview](/fdc/overview). To bridge the separate script executions, we will save the relevant data of each script to a file in the `dirPath` folder. Each succeeding script will then read that file to load the data. ## Prepare request A JSON request to the verifier follows the same structure for all attestation types, with field values varying per type. ### Required Fields - `attestationType`: UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId`: UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody`: Specific to each attestation type. For `AddressValidity`, `requestBody` contains a single field: - `addressString`: The address to verify. ### Reference Documentation - [AddressValidity Specification](/fdc/attestation-types/address-validity) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest) - API available for [DOGE](https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest) and [XRP](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest). ### Example Values - `attestationType`: UTF8 hex encoding of `AddressValidity`, zero-padded to 32 bytes. - `sourceId`: UTF8 hex encoding of `testBTC`, zero-padded to 32 bytes. - `"test"` prefix denotes Bitcoin testnet. - Supports deployment on Flare testchains (`Coston` or `Coston2`). - Replace `testBTC` with `testDOGE` or `testXRP` for other chains. - `addressString`: `mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs`. ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHexString`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) within the [example repository](https://github.com/flare-foundation/flare-foundry-starter), but they can also be defined locally in your contract or script. ```solidity title="script/fdcExample/Base.s.sol" function toHexString( bytes memory data ) public pure returns (string memory) { bytes memory alphabet = "0123456789abcdef"; bytes memory str = new bytes(2 + data.length * 2); str[0] = "0"; str[1] = "x"; for (uint i = 0; i < data.length; i++) { str[2 + i * 2] = alphabet[uint(uint8(data[i] >> 4))]; str[3 + i * 2] = alphabet[uint(uint8(data[i] & 0x0f))]; } return string(str); } ``` ```solidity title="script/fdcExample/Base.s.sol" function toUtf8HexString( string memory _string ) internal pure returns (string memory) { string memory encodedString = toHexString( abi.encodePacked(_string) ); uint256 stringLength = bytes(encodedString).length; require(stringLength <= 64, "String too long"); uint256 paddingLength = 64 - stringLength + 2; for (uint256 i = 0; i < paddingLength; i++) { encodedString = string.concat(encodedString, "0"); } return encodedString; } ``` We also define a helper function for formatting data into a JSON string. ```solidity title="script/fdcExample/Base.s.sol" function prepareAttestationRequest( string memory attestationType, string memory sourceId, string memory requestBody ) internal view returns (string[] memory, string memory) { // We read the API key from the .env file string memory apiKey = vm.envString("VERIFIER_API_KEY_TESTNET"); // Preparing headers string[] memory headers = prepareHeaders(apiKey); // Preparing body string memory body = prepareBody( attestationType, sourceId, requestBody ); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); console.log("body: %s\n", body); return (headers, body); } function prepareHeaders( string memory apiKey ) internal pure returns (string[] memory) { string[] memory headers = new string[](2); headers[0] = string.concat('"X-API-KEY": ', apiKey); headers[1] = '"Content-Type": "application/json"'; return headers; } function prepareBody( string memory attestationType, string memory sourceId, string memory body ) internal pure returns (string memory) { return string.concat( '{"attestationType": ', '"', attestationType, '"', ', "sourceId": ', '"', sourceId, '"', ', "requestBody": ', body, "}" ); } ``` In the example repository, these are once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) library file. Thus, the part of the script that prepares the verifier request looks like: ```solidity title="script/fdcExample/AddressValidity.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "AddressValidity"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { using Surl for *; // Setting request data string public addressStr = "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs"; // Id of the Bitcoin address to be validated string public baseSourceName = "btc"; // Part of verifier URL string public sourceName = "testBTC"; // Bitcoin chain ID function prepareRequestBody( string memory addressStr ) private pure returns (string memory) { return string.concat('{"addressStr": "', addressStr, '"}'); } function run() external { // Preparing request data string memory attestationType = Base.toUtf8HexString( attestationTypeName ); string memory sourceId = Base.toUtf8HexString(sourceName); string memory requestBody = prepareRequestBody(addressStr); (string[] memory headers, string memory body) = Base .prepareAttestationRequest(attestationType, sourceId, requestBody); // TODO change key in .env // string memory baseUrl = "https://testnet-verifier-fdc-test.aflabs.org/"; string memory baseUrl = vm.envString("VERIFIER_URL_TESTNET"); string memory url = string.concat( baseUrl, "verifier/", baseSourceName, "/", attestationTypeName, "/prepareRequest" ); console.log("url: %s", url); (string[] memory headers, string memory body) = prepareAttestationRequest(attestationType, sourceId, requestBody); ... } } ... ``` If you are accessing a different chain, replace the `baseSourceName` with an appropriate value, `doge` or `xrp`. The code above differs slightly from the [starter example](https://github.com/flare-foundation/flare-foundry-starter). But, if we remove the ellipses `...` signifying missing code, we can still run the script. Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest) to check what the response is. We can run the script by calling the following commands in the console. ```bash source .env ``` ```bash forge script script/fdcExample/AddressValidity.s.sol:PrepareAttestationRequest --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --broadcast --ffi ``` The prerequisite for this is that the `.env` file is not missing the `PRIVATE KEY` and `COSTON2_RPC_URL` values. The script can also access other chains; that can be achieved by replacing the `--rpc-url` value with `COSTON_RPC_URL`, `FLARE_RPC_URL`, or `SONGBIRD_RPC_URL`. ## Post request to verifier Before submitting address validation requests to the FDC protocol, we first need to prepare and send them to a verifier server. This section walks through the request submission process using the `surl` package. We place `using Surl for *;` at the start of our `PostRequest` contract, and then call its `post` method on the verifier URL. ```solidity title="script/fdcExample/AddressValidity.s.sol" (, bytes memory data) = url.post(headers, body); ``` We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/` the path `verifier/btc_testnet4/AddressValidity/prepareRequest`. We can do so dynamically with the following code. ```solidity title="script/fdcExample/AddressValidity.s.sol" string memory baseUrl = "https://fdc-verifiers-testnet.flare.network/"; string memory url = string.concat( baseUrl, "verifier/", baseSourceName, "/", attestationTypeName, "/prepareRequest" ); console.log("url: %s", url); string memory requestBody = string.concat( '{"addressStr": "', addressStr, '"}' ); ``` Lastly, we parse the return data from the verifier server. Using the Foundry `parseJson` shortcode, and a custom struct `AttestationResponse`, we decode the returned data and extract from it the ABI encoded request. ```solidity title="script/fdcExample/Base.s.sol" function parseAttestationRequest( bytes memory data ) internal pure returns (AttestationResponse memory) { string memory dataString = string(data); bytes memory dataJson = vm.parseJson(dataString); AttestationResponse memory response = abi.decode( dataJson, (AttestationResponse) ); console.log("response status: %s\n", response.status); console.log("response abiEncodedRequest: "); console.logBytes(response.abiEncodedRequest); console.log("\n"); return response; } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 4164647265737356616c69646974790000000000000000000000000000000000 7465737442544300000000000000000000000000000000000000000000000000 7d2ef938d4ffd2392f588bf46563e07ab885b15fead91c1bb99b16f465b71a68 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("AddressValidity")` - **Second line:** `toUtf8HexString("testBTC")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `AddressValidity.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
We write the `abiEncodedRequest` to a file (`data/AddressValidity_abiEncodedRequest.txt`) to it in the next step. ```solidity title="script/fdcExample/AddressValidity.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_abiEncodedRequest"), StringsBase.toHexString(response.abiEncodedRequest), true ); ``` ## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. Now, we submit the validated request to the blockchain using deployed smart contracts. ### Submit request The entire submission process requires only five key steps: ```solidity title="script/fdcExample/Base.s.sol" function submitAttestationRequest( bytes memory abiEncodedRequest ) internal { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( abiEncodedRequest ); console.log("request fee: %s\n", requestFee); vm.stopBroadcast(); vm.startBroadcast(deployerPrivateKey); IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); fdcHub.requestAttestation{value: requestFee}(abiEncodedRequest); vm.stopBroadcast(); } ``` ### Step-by-Step Breakdown 1. Load Private Key The private key is read from the `.env` file using Foundry's `envUint` function: ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); ``` 2. Obtain Request Fee We retrieve the required requestFee from the `FdcRequestFeeConfigurations` contract: ```solidity IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( response.abiEncodedRequest ); ``` This is done in a separate broadcast to ensure `requestFee` is available before submitting the request. 3. Access `FdcHub` Contract Using the `ContractRegistry` library (from `flare-periphery`), we fetch the `FdcHub` contract: ```solidity IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); ``` 4. Submit the Attestation Request We send the attestation request with the required fee: ```solidity fdcHub.requestAttestation{value: requestFee}(response.abiEncodedRequest); ``` 5. Calculate the Voting Round Number To determine the voting round in which the attestation request is processed, we query the `FlareSystemsManager` contract: ```solidity // Calculating roundId IFlareSystemsManager flareSystemsManager = ContractRegistry .getFlareSystemsManager(); uint32 roundId = flareSystemsManager.getCurrentVotingEpochId(); console.log("roundId: %s\n", Strings.toString(roundId)); ``` This can be done within the existing broadcast or in a new one (as done in the demo repository for better code organization). Again, we write the `roundId` to a file (`data/AddressValidity_roundId.txt`). ## Wait for response We wait for the round to finalize. This takes no more than 180 seconds. You can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. If you want to learn more about how the FDC protocol works, check [here](/fdc/overview). ## Prepare proof request We prepare the proof request in a similar manner as in the step Prepare the request, by string concatenation. We import two new variables from the `.env` file; the URL of a verifier server and the corresponding API key. note ```solidity title="script/fdcExample/AddressValidity.s.sol" string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); ``` Also, by repeatedly using the Foundry shortcode `vm.readLine`, we read the data, saved to a file in the previous step, to variables. ```solidity string memory votingRoundId = vm.readLine(filePath); string memory requestBytes = vm.readLine(filePath); ``` The code is as follows. ```solidity title="script/fdcExample/AddressValidity.s.sol" contract RetrieveDataAndProof is Script { using Surl for *; function run() external { string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); console.log("votingRoundId: %s\n", votingRoundId); console.log("requestBytes: %s\n", requestBytes); string[] memory headers = Base.prepareHeaders(apiKey); string memory body = string.concat( '{"votingRoundId":', votingRoundId, ',"requestBytes":"', requestBytes, '"}' ); console.log("body: %s\n", body); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); ... } } ``` ## Post proof request to DA Layer We post the proof request to a chosen DA Layer provider server also with the same code as we did in the previous step. ```solidity title="script/fdcExample/AddressValidity.s.sol" string memory url = string.concat( daLayerUrl, // "api/v0/fdc/get-proof-round-id-bytes" "api/v1/fdc/proof-by-request-round-raw" ); console.log("url: %s\n", url); (, bytes memory data) = postAttestationRequest(url, headers, body); ``` Parsing the returned data requires the definition of an auxiliary `struct`. {/* */} ```solidity title="script/fdcExample/Base.s.sol" struct ParsableProof { bytes32 attestationType; bytes32[] proofs; bytes responseHex; } ``` The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x4164647265737356616c69646974790000000000000000000000000000000000`. The array `proofs` holds the Merkle proofs of our attestation request. Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IAddressValidity.Response` struct. We retrieve this data as follows. ```solidity title="script/fdcExample/AddressValidity.s.sol" bytes memory dataJson = Base.parseData(data); ParsableProof memory proof = abi.decode(dataJson, (ParsableProof)); IAddressValidity.Response memory proofResponse = abi.decode( proof.responseHex, (IAddressValidity.Response) ); ```
An example complete proof response and decoded `IAddressValidity.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: "0x 0000000000000000000000000000000000000000000000000000000000000020 4164647265737356616c69646974790000000000000000000000000000000000 7465737442544300000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6bda 000000000000000000000000000000000000000000000000ffffffffffffffff 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000140 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000060 6810e152510fe893f9cc8954c4dfaecd5c2be00e2732d6fe3e25922f30c5a3c5 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000", attestation_type: "0x4164647265737356616c69646974790000000000000000000000000000000000", proof: [ "0x275dc338dd4e6a0a8749caa098c6749e0e77e22ba9db264f334b5dfb79aa6321", "0x084e002bbe12f4a163d82ddd17861d1d3131c816fe3b998d575d134043a6c8f1", "0xc30304c7d430e3d0f83d05017035f13ca19dec2799917745967f4c48685eab49", "0x4d622137c9e7c9a1fa3a5d2942a183a8e926ba8659fe606495ea994acbb6ec0f" ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IAddressValidity.Response` struct is: ```shell [ attestationType: "0x4164647265737356616c69646974790000000000000000000000000000000000", sourceId: "0x7465737442544300000000000000000000000000000000000000000000000000", votingRound: "945114", lowestUsedTimestamp: "18446744073709551615", requestBody: [ "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs", addressStr: "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs" ], responseBody: [ true, "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs", "0x6810e152510fe893f9cc8954c4dfaecd5c2be00e2732d6fe3e25922f30c5a3c5", isValid: true, standardAddress: "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs", standardAddressHash: "0x6810e152510fe893f9cc8954c4dfaecd5c2be00e2732d6fe3e25922f30c5a3c5" ] ] ```
## Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. To verify address validity, we first format our data using the `IAddressValidity.Proof` struct, which contains both the Merkle proof and the response data. ```solidity title="script/fdcExample/AddressValidity.s.sol" IAddressValidity.Proof memory _proof = IAddressValidity.Proof( proof.proofs, proofResponse ); ``` We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If we proof is valid, the function `verifyAddressValidity` will return `true`, otherwise `false`. As before, we wrap the whole thing into a broadcast environment, using the `PRIVATE_KEY` variable from our `.env` file. ```solidity title="script/fdcExample/AddressValidity.s.sol" uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); bool isValid = ContractRegistry .getFdcVerification() .verifyAddressValidity(proof); console.log("proof is valid: %s\n", StringsBase.toString(isValid)); vm.stopBroadcast(); ``` ## Use the data We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive an address and proof, and decide if the address is valid. If the address is valid, the contract will add it to an array of valid addresses. Otherwise, it will raise an error. The code for this contract is as follows. ```solidity title="src/fdcExample/AddressValidity.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; struct EventInfo { address sender; uint256 value; bytes data; } contract AddressValidity { string[] public verifiedAddresses; function isAddressValidityProofValid( IAddressValidity.Proof calldata transaction ) public view returns (bool) { // Use the library to get the verifier contract and verify that this transaction was proved by the FDC IFdcVerification fdc = ContractRegistry.getFdcVerification(); console.log("transaction: %s\n", FdcStrings.toJsonString(transaction)); // return true; return fdc.verifyAddressValidity(transaction); } function registerAddress( string calldata _addressStr, IAddressValidity.Proof calldata _transaction ) external { // 1. FDC Logic // Check that this AddressValidity has indeed been confirmed by the FDC require( isAddressValidityProofValid(_transaction), "Invalid transaction proof" ); // 2. Business logic string provedAddress = _transaction.data.requestBody.addressStr; require( Strings.equal(provedAddress, _addressStr), string.concat( "Invalid address.\n\tProvided: ", _addressStr, "\n\tProoved: ", provedAddress ) ); verifiedAddresses.push(provedAddress); } } ``` The function `registerAddress` takes as parameters a string representing an address, and a proof. If the proof is valid, and if the given address matches the one in the proof, the address is added to an array of verified addresses. We deploy the contract through a simple script. The script creates a new `AddressRegistry` contract, and writes its address to a file (`data/Address_listenerAddress.txt`). ```solidity title="script/fdcExample/Address.s.sol" contract DeployContract is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); AddressRegistry addressRegistry = new AddressRegistry(); address _address = address(addressRegistry); vm.stopBroadcast(); Base.writeToFile( dirPath, string.concat(attestationTypeName, "_address"), StringsBase.toHexString(abi.encodePacked(_address)), true ); } } ``` We run the above script with the following console command. ```bash forge script script/fdcExample/AddressValidity.s.sol:Deploy --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` Lastly, we define a script that interacts with the above contract. It first reads the ABI encoded proof data, and the contract address, from files. Then, it connects to the above contract at the saved address (this is why we require the interface). With that, it is able to call the `registerAddress` method of the contract. ```solidity title="script/fdcExample/AddressValidity.s.sol" contract InteractWithContract is Script { function run() external { string memory addressString = vm.readLine( string.concat(dirPath, attestationTypeName, "_address", ".txt") ); address _address = vm.parseAddress(addressString); string memory proofString = vm.readLine( string.concat(dirPath, attestationTypeName, "_proof", ".txt") ); bytes memory proofBytes = vm.parseBytes(proofString); IAddressValidity.Proof memory proof = abi.decode( proofBytes, (IAddressValidity.Proof) ); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IAddressRegistry registry = IAddressRegistry(_address); registry.registerAddress("mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs", proof); vm.stopBroadcast(); } } ``` We run this script with the console command: ```bash forge script script/fdcExample/ AddressValidity.s.sol:InteractWithContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` --- ## EVMTransaction(Foundry) The [`EVMTransaction`](/fdc/attestation-types/evm-transaction) attestation type enables data collection about a transaction on an EVM chain. The currently supported chain are: `ETH`, `FLR`, and `SGB`. You can learn more about it in the official [specification repo](/fdc/attestation-types/evm-transaction). We will now demonstrate how the FDC protocol can be used to collect the data of a given Ethereum transaction. The transaction we will be observing has the hash `0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c`; this is an arbitrary transaction that we acquired from the Sepolia Ethereum testnet [explorer](https://sepolia.etherscan.io/). The same procedure works for all supported sources, `ETH`, `FLR`, and `SGB`. The source then requires only a slight modification; we will remind you of that when it comes up in the guide. In this guide, we will be following the steps outlined in the [FDC Overview](/fdc/overview). Our implementation requires handling the FDC voting round finalization process. To manage this, we will create separate scripts in `script/fdcExample/EVMTransaction.s.sol` that handle different stages of the validation process: ```solidity title="script/fdcExample/EVMTransaction.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "EVMTransaction"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { ... } contract SubmitAttestationRequest is Script { ... } contract RetrieveDataAndProof is Script { ... } contract Deploy is Script { ... } ... ``` The names of included contracts mostly mirror the steps described in the [FDC guide](/fdc/overview). To bridge the separate executions of the scripts, we will save the relevant data of each script to a file in the `dirPath` folder. Each succeeding script will then read that file to load the data. ## Prepare request The JSON request to the verifier is the same form for all attestation types, but the values of the fields differ between them. It contains the following fields. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `EVMTransaction`, `requestBody` is a JSON containing the fields: - `transactionHash`: hash (address) of the observed transaction - `requiredConfirmations`: the depth of the block containing the transaction at which it is considered confirmed, i.e. when the transaction itself is considered confirmed; as `uint16` - `provideInput`: a `bool` determining whether the `input` field is included in the response - `listEvents`: a `bool` determining whether the `events` field is included in the response - `logIndices`: an `uint32` array of indices of the events to be included in the response; if `listEvents` is set to false `false` and this field is not `[]`, the attestation request will fail ### Reference Documentation - [EVMTransaction Specification](/fdc/attestation-types/evm-transaction) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/api-doc#/) ### Example Values - `transactionHash`: `0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c` - `requiredConfirmations`: `1` - `provideInput`: `true` - `listEvents`: `true` - `logIndices`: `[]` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHexString`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) within the [example repository](https://github.com/flare-foundation/flare-foundry-starter), but they can also be defined locally in your contract or script. The first function translates a string to a UTF8 encoded hex string. The other then zero-right-pads such a string, so that it is 32 bytes long. ```solidity title="script/fdcExample/Base.s.sol" function toHexString( bytes memory data ) public pure returns (string memory) { bytes memory alphabet = "0123456789abcdef"; bytes memory str = new bytes(2 + data.length * 2); str[0] = "0"; str[1] = "x"; for (uint i = 0; i < data.length; i++) { str[2 + i * 2] = alphabet[uint(uint8(data[i] >> 4))]; str[3 + i * 2] = alphabet[uint(uint8(data[i] & 0x0f))]; } return string(str); } ``` ```solidity title="script/fdcExample/Base.s.sol" function toUtf8HexString( string memory _string ) internal pure returns (string memory) { string memory encodedString = toHexString( abi.encodePacked(_string) ); uint256 stringLength = bytes(encodedString).length; require(stringLength <= 64, "String too long"); uint256 paddingLength = 64 - stringLength + 2; for (uint256 i = 0; i < paddingLength; i++) { encodedString = string.concat(encodedString, "0"); } return encodedString; } ``` We also define a helper function for formatting data into a JSON string. ```solidity title="script/fdcExample/Base.s.sol" function prepareAttestationRequest( string memory attestationType, string memory sourceId, string memory requestBody ) internal view returns (string[] memory, string memory) { // We read the API key from the .env file string memory apiKey = vm.envString("VERIFIER_API_KEY_TESTNET"); // Preparing headers string[] memory headers = prepareHeaders(apiKey); // Preparing body string memory body = prepareBody( attestationType, sourceId, requestBody ); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); console.log("body: %s\n", body); return (headers, body); } function prepareHeaders( string memory apiKey ) internal pure returns (string[] memory) { string[] memory headers = new string[](2); headers[0] = string.concat('"X-API-KEY": ', apiKey); headers[1] = '"Content-Type": "application/json"'; return headers; } function prepareBody( string memory attestationType, string memory sourceId, string memory body ) internal pure returns (string memory) { return string.concat( '{"attestationType": ', '"', attestationType, '"', ', "sourceId": ', '"', sourceId, '"', ', "requestBody": ', body, "}" ); } ``` In the example repository, these are once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) library file. Thus, the part of the script that prepares the verifier request looks like: ```solidity title="script/fdcExample/EVMTransaction.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "EVMTransaction"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { using Surl for *; // Setting request data string public transactionHash = "0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c"; string public requiredConfirmations = "1"; string public provideInput = "true"; string public listEvents = "true"; string public logIndices = "[]"; string public sourceName = "testETH"; // Bitcoin chain ID string public baseSourceName = "eth"; // Part of verifier URL function prepareRequestBody( string memory transactionHash, string memory requiredConfirmations, string memory provideInput, string memory listEvents, string memory logIndices ) private pure returns (string memory) { return string.concat( '{"transactionHash": ', '"', transactionHash, '"', ', "requiredConfirmations": ', '"', requiredConfirmations, '"', ', "provideInput": ', provideInput, ', "listEvents": ', listEvents, ', "logIndices": ', logIndices, "}" ); } function run() external { // Preparing request data string memory attestationType = toUtf8HexString( attestationTypeName ); string memory sourceId = toUtf8HexString(sourceName); string memory requestBody = prepareRequestBody( transactionHash, requiredConfirmations, provideInput, listEvents, logIndices ); (string[] memory headers, string memory body) = prepareAttestationRequest(attestationType, sourceId, requestBody); ... } } ... ``` If you are accessing a different chain, replace the `baseSourceName` with an appropriate value, `flr` or `sgb`. The code above differs slightly from the [starter example](https://github.com/flare-foundation/flare-foundry-starter). But, if we remove the ellipses `...` signifying missing code, we can still run the script. Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/eth/EVMTransaction/prepareRequest) to check what the response will be. We can run the script by calling the following commands in the console. ```bash source .env ``` ```bash forge script script/fdcExample/EVMTransaction.s.sol:PrepareAttestationRequest --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` The prerequisite for this is that the `.env` file is not missing the `PRIVATE KEY` and `COSTON2_RPC_URL` values. The script can also access other chains; that can be achieved by replacing the `--rpc-url` value with `COSTON_RPC_URL`, `FLARE_RPC_URL`, or `SONGBIRD_RPC_URL`. ## Post request to verifier Before submitting address validation requests to the FDC protocol, we first need to prepare and send them to a verifier server. This section walks through the request submission process using the `surl` package. We place `using Surl for *;` at the start of our `PostRequest` contract, and then call its `post` method on the verifier URL. ```solidity title="script/fdcExample/EVMTransaction.s.sol" (, bytes memory data) = url.post(headers, body); ``` We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/` the path `/verifier/eth/EVMTransaction/prepareRequest`. We can do so dynamically with the following code. ```solidity title="script/fdcExample/EVMTransaction.s.sol" string memory baseUrl = "https://fdc-verifiers-testnet.flare.network/"; string memory url = string.concat( baseUrl, "verifier/", baseSourceName, "/", attestationTypeName, "/prepareRequest" ); console.log("url: %s", url); string memory requestBody = string.concat( '{"addressStr": "', addressStr, '"}' ); ``` Lastly, we parse the return data from the verifier server. Using the Foundry `parseJson` shortcode, and a custom struct `AttestationResponse`, we decode the returned data and extract from it the ABI encoded request. ```solidity title="script/fdcExample/Base.s.sol" function parseAttestationRequest( bytes memory data ) internal pure returns (AttestationResponse memory) { string memory dataString = string(data); bytes memory dataJson = vm.parseJson(dataString); AttestationResponse memory response = abi.decode( dataJson, (AttestationResponse) ); console.log("response status: %s\n", response.status); console.log("response abiEncodedRequest: "); console.logBytes(response.abiEncodedRequest); console.log("\n"); return response; } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 45564d5472616e73616374696f6e000000000000000000000000000000000000 7465737445544800000000000000000000000000000000000000000000000000 9d410778cc0b2b8f1b8eaa79cbd0eed5d3be7514dea070e2041ad00a4c6e88f8 0000000000000000000000000000000000000000000000000000000000000020 4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("EVMTransaction")` - **Second line:** `toUtf8HexString("testETH")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `EVMTransaction.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
We write the `abiEncodedRequest` to a file (`data/EVMTransaction_abiEncodedRequest.txt`) to it in the next step. ```solidity title="script/fdcExample/EVMTransaction.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_abiEncodedRequest"), StringsBase.toHexString(response.abiEncodedRequest), true ); ``` ## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. Now, we submit the validated request to the blockchain using deployed smart contracts. ### Submit request The entire submission process requires only five key steps: ```solidity title="script/fdcExample/Base.s.sol" function submitAttestationRequest(bytes memory abiEncodedRequest) internal { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( abiEncodedRequest ); console.log("request fee: %s\n", requestFee); vm.stopBroadcast(); vm.startBroadcast(deployerPrivateKey); // address fdcHubAddress = 0x48aC463d7975828989331F4De43341627b9c5f1D; IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); fdcHub.requestAttestation{value: requestFee}(abiEncodedRequest); vm.stopBroadcast(); } ``` ### Step-by-Step Breakdown 1. Load Private Key The private key is read from the `.env` file using Foundry's `envUint` function: ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); ``` 2. Obtain Request Fee We retrieve the required requestFee from the `FdcRequestFeeConfigurations` contract: ```solidity IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( response.abiEncodedRequest ); ``` This is done in a separate broadcast to ensure `requestFee` is available before submitting the request. 3. Access `FdcHub` Contract Using the `ContractRegistry` library (from `flare-periphery`), we fetch the `FdcHub` contract: ```solidity IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); ``` 4. Submit the Attestation Request We send the attestation request with the required fee: ```solidity fdcHub.requestAttestation{value: requestFee}(response.abiEncodedRequest); ``` 5. Calculate the Voting Round Number To determine the voting round in which the attestation request is processed, we query the `FlareSystemsManager` contract: ```solidity // Calculating roundId IFlareSystemsManager flareSystemsManager = ContractRegistry .getFlareSystemsManager(); uint32 roundId = flareSystemsManager.getCurrentVotingEpochId(); console.log("roundId: %s\n", Strings.toString(roundId)); ``` This can be done within the existing broadcast or in a new one (as done in the demo repository for better code organization). Again, we write the `roundId` to a file (`data/EVMTransaction_roundId.txt`). ## Wait for response We wait for the round to finalize. This takes no more than 180 seconds. You can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. If you want to learn more about how the FDC protocol works, check [here](/fdc/overview). ## Prepare proof request We prepare the proof request in a similar manner as in the step Prepare the request, by string concatenation. We import two new variables from the `.env` file; the URL of a verifier server and the corresponding API key. ```solidity title="script/fdcExample/EVMTransaction.s.sol" string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); ``` Also, by repeatedly using the Foundry shortcode `vm.readLine`, we read the data, saved to a file in the previous step, to variables. ```solidity title="script/fdcExample/EVMTransaction.s.sol" string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); ``` The code is as follows. ```solidity title="script/fdcExample/EVMTransaction.s.sol" contract RetrieveDataAndProof is Script { using Surl for *; function run() external { string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); console.log("votingRoundId: %s\n", votingRoundId); console.log("requestBytes: %s\n", requestBytes); string[] memory headers = Base.prepareHeaders(apiKey); string memory body = string.concat( '{"votingRoundId":', votingRoundId, ',"requestBytes":"', requestBytes, '"}' ); console.log("body: %s\n", body); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); ... } } ``` ## Post proof request to DA Layer We post the proof request to a chosen DA Layer provider server also with the same code as we did in the previous step. ```solidity title="script/fdcExample/EVMTransaction.s.sol" string memory url = string.concat( daLayerUrl, // "api/v0/fdc/get-proof-round-id-bytes" "api/v1/fdc/proof-by-request-round-raw" ); console.log("url: %s\n", url); (, bytes memory data) = Base.postAttestationRequest(url, headers, body); ``` Parsing the returned data requires the definition of an auxiliary `struct`. {/* */} ```solidity title="script/fdcExample/Base.s.sol" struct ParsableProof { bytes32 attestationType; bytes32[] proofs; bytes responseHex; } ``` The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x45564d5472616e73616374696f6e000000000000000000000000000000000000`. The array `proofs` holds the Merkle proofs of our attestation request. Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IEVMTransaction.Response` struct. We retrieve this data as follows. ```solidity title="script/fdcExample/EVMTransaction.s.sol" bytes memory dataJson = parseData(data); ParsableProof memory proof = abi.decode(dataJson, (ParsableProof)); IEVMTransaction.Response memory proofResponse = abi.decode( proof.responseHex, (IEVMTransaction.Response) ); ```
An example complete proof response and decoded `IEVMTransaction.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 0000000000000000000000000000000000000000000000000000000000000020 45564d5472616e73616374696f6e000000000000000000000000000000000000 7465737445544800000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6c2c 0000000000000000000000000000000000000000000000000000000067724b20 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000180 4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000070acc6 0000000000000000000000000000000000000000000000000000000067724b20 00000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a806 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 00000000000000000000000000000000000000000000000000d8b72d434c8000 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000520 00000000000000000000000000000000000000000000000000000000000003c5 3593564c00000000000000000000000000000000000000000000000000000000 0000006000000000000000000000000000000000000000000000000000000000 000000a000000000000000000000000000000000000000000000000000000000 6772521a00000000000000000000000000000000000000000000000000000000 000000040b000604000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000000400000000000000000000000000000000000000000000000000000000 0000008000000000000000000000000000000000000000000000000000000000 000000e000000000000000000000000000000000000000000000000000000000 0000020000000000000000000000000000000000000000000000000000000000 0000028000000000000000000000000000000000000000000000000000000000 0000004000000000000000000000000000000000000000000000000000000000 0000000200000000000000000000000000000000000000000000000000d8b72d 434c800000000000000000000000000000000000000000000000000000000000 0000010000000000000000000000000000000000000000000000000000000000 0000000200000000000000000000000000000000000000000000000000d8b72d 434c800000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 000000a000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41c7d4b196c b0c7b01d743fbc6116a902379c72380000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902 379c7238000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57a b7e35b9500000000000000000000000000000000000000000000000000000000 0000001900000000000000000000000000000000000000000000000000000000 000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902 379c723800000000000000000000000070ad32b82b4fe2821c798e628d936452 18e2a80600000000000000000000000000000000000000000000000000000000 ad2090e40c000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000006 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000200 0000000000000000000000000000000000000000000000000000000000000360 00000000000000000000000000000000000000000000000000000000000004c0 00000000000000000000000000000000000000000000000000000000000006a0 0000000000000000000000000000000000000000000000000000000000000800 000000000000000000000000000000000000000000000000000000000000003f 000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000100 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000d8b72d434c8000 0000000000000000000000000000000000000000000000000000000000000040 0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003289680dd4d6c10bb19b899729cda5eef58aeff1 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000ae6dcda8 0000000000000000000000000000000000000000000000000000000000000041 000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000003289680dd4d6c10bb19b899729cda5eef58aeff1 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000d8b72d434c8000 0000000000000000000000000000000000000000000000000000000000000042 0000000000000000000000003289680dd4d6c10bb19b899729cda5eef58aeff1 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 00000000000000000000000000000000000000000000000000000000000000a0 ffffffffffffffffffffffffffffffffffffffffffffffffffffffff51923258 00000000000000000000000000000000000000000000000000d8b72d434c8000 00000000000000000000000000000000000011d79ac448fce087b0605d7423c8 000000000000000000000000000000000000000000000000002231596d817570 000000000000000000000000000000000000000000000000000000000002925f 0000000000000000000000000000000000000000000000000000000000000043 0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57ab7e35b95 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000006fa26f 0000000000000000000000000000000000000000000000000000000000000044 0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 00000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a806 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000adfe2b39', attestation_type: '0x45564d5472616e73616374696f6e000000000000000000000000000000000000', proof: [ '0x9251c0e3047688af1305daf61f2b757527b731e7d1fad3c71d08734772fbebeb', '0xad5fdf0f8cb6bc42cdab5affb8f03a1fadaf1ef60875af76344b7ca3ab694c9b', '0xead06bac3be86604034e138784c86f0b14f2481c001e31e17d03c185488033dc' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IEVMTransaction.Response` struct is: ```shell [ attestationType: '0x45564d5472616e73616374696f6e000000000000000000000000000000000000', sourceId: '0x7465737445544800000000000000000000000000000000000000000000000000', votingRound: '945196', lowestUsedTimestamp: '1735543584', requestBody: [ '0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c', '1', true, true, [], transactionHash: '0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c', requiredConfirmations: '1', provideInput: true, listEvents: true, logIndices: [] ], responseBody: [ '7384262', '1735543584', '0x70Ad32B82B4FE2821C798e628d93645218E2A806', false, '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', '61000000000000000', '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772521a00000000000000000000000000000000000000000000000000000000000000040b000604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57ab7e35b95000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a80600000000000000000000000000000000000000000000000000000000ad2090e40c', '1', [ ... ], blockNumber: '7384262', timestamp: '1735543584', sourceAddress: '0x70Ad32B82B4FE2821C798e628d93645218E2A806', isDeployment: false, receivingAddress: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', value: '61000000000000000', input: '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772521a00000000000000000000000000000000000000000000000000000000000000040b000604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57ab7e35b95000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a80600000000000000000000000000000000000000000000000000000000ad2090e40c', status: '1', events: [ ... ] ] ] ```
## Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. To verify address validity, we first format our data using the `IEVMTransaction.Proof` struct, which contains both the Merkle proof and the response data. ```solidity title="script/fdcExample/EVMTransaction.s.sol" IEVMTransaction.Proof memory _proof = IEVMTransaction.Proof( proof.proofs, proofResponse ); ``` We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If we proof is valid, the function `verifyEVMTransaction` will return `true`, otherwise `false`. As before, we wrap the whole thing into a broadcast environment, using the `PRIVATE_KEY` variable from our `.env` file. ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); bool isValid = ContractRegistry .getFdcVerification() .verifyEVMTransaction(proof); console.log("proof is valid: %s\n", StringsBase.toString(isValid)); vm.stopBroadcast(); ``` In actuality, we will only verify the proof within a deployed contract, which we will define in the next step. What we will do here instead is, we will save the proof to a file so that it can be later loaded into a variable. The code that does this is as follows. ```solidity title="script/fdcExample/EVMTransaction.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_proof"), StringsBase.toHexString(abi.encode(_proof)), true ); ``` ## Use the data We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive data and proof of an Ethereum transaction, and store all token transfers contained into an array of `TokenTransfer` structs. It will do so only if the transaction is valid. ```solidity title="src/fdcExample/EVMTransaction.sol" struct TokenTransfer { address from; address to; uint256 value; } ``` First, we define an interface that the contract will inherit from. We do so, so that we may contact the contract later through a script. ```solidity title="src/fdcExample/EVMTransaction.sol" interface ITransferEventListener { function collectTransferEvents( IEVMTransaction.Proof calldata _transaction ) external; } ``` The interface exposes the only function the script will call, `collectTransferEvents`. We now define the contract as follows. ```solidity title="src/fdcExample/EVMTransaction.sol" contract TransferEventListener is ITransferEventListener { TokenTransfer[] public tokenTransfers; address public USDC_CONTRACT = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238; // USDC contract address on sepolia function isEVMTransactionProofValid( IEVMTransaction.Proof calldata transaction ) public view returns (bool) { // Use the library to get the verifier contract and verify that this transaction was proved by the FDC IFdcVerification fdc = ContractRegistry.getFdcVerification(); return fdc.verifyEVMTransaction(transaction); } function collectTransferEvents( IEVMTransaction.Proof calldata _transaction ) external { // 1. FDC Logic // Check that this EVMTransaction has indeed been confirmed by the FDC require( isEVMTransactionProofValid(_transaction), "Invalid transaction proof" ); // 2. Business logic // Go through all events for ( uint256 i = 0; i < _transaction.data.responseBody.events.length; i++ ) { // Get current event IEVMTransaction.Event memory _event = _transaction .data .responseBody .events[i]; // Disregard events that are not from the USDC contract if (_event.emitterAddress != USDC_CONTRACT) { continue; } // Disregard non Transfer events if ( _event.topics.length == 0 || // No topics // The topic0 doesn't match the Transfer event _event.topics[0] != keccak256(abi.encodePacked("Transfer(address,address,uint256)")) ) { continue; } // We now know that this is a Transfer event from the USDC contract - and therefore know how to decode topics and data // Topic 1 is the sender address sender = address(uint160(uint256(_event.topics[1]))); // Topic 2 is the receiver address receiver = address(uint160(uint256(_event.topics[2]))); // Data is the amount uint256 value = abi.decode(_event.data, (uint256)); // Add the transfer to the list tokenTransfers.push( TokenTransfer({from: sender, to: receiver, value: value}) ); } } function getTokenTransfers() external view returns (TokenTransfer[] memory) { TokenTransfer[] memory result = new TokenTransfer[]( tokenTransfers.length ); for (uint256 i = 0; i < tokenTransfers.length; i++) { result[i] = tokenTransfers[i]; } return result; } } ``` We deploy the contract through a simple script. The script creates a new `TransferEventListener` contract, and writes its address to a file (`data/EVMTransaction_address.txt`). ```solidity title="script/fdcExample/EVMTransaction.s.sol" contract DeployContract is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); TransferEventListener listener = new TransferEventListener(); address listenerAddress = address(listener); vm.stopBroadcast(); Base.writeToFile( dirPath, string.concat(attestationTypeName, "_address"), StringsBase.toHexString(abi.encodePacked(listenerAddress)), true ); } } ``` We deploy the contract with the following console command. ```bash forge script script/fdcExample/EVMTransaction.s.sol:DeployContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --broadcast --verify --verifier blockscout --verifier-url $COSTON2_EXPLORER_API --ffi ``` Lastly, we define a script that interacts with the above contract. It first reads the ABI encoded proof data, and the contract address, from files. Then, it connects to the above contract at the saved address (this is why we require the interface). With that, it is able to call the `getTokenTransfers` method of the contract. ```solidity title="script/fdcExample/EVMTransaction.s.sol" contract InteractWithContract is Script { function run() external { string memory addressString = vm.readLine( string.concat( dirPath, attestationTypeName, "_address", ".txt" ) ); address listenerAddress = vm.parseAddress(addressString); string memory proofString = vm.readLine( string.concat(dirPath, attestationTypeName, "_proof", ".txt") ); bytes memory proofBytes = vm.parseBytes(proofString); IEVMTransaction.Proof memory proof = abi.decode( proofBytes, (IEVMTransaction.Proof) ); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); ITransferEventListener listener = ITransferEventListener( listenerAddress ); console.logAddress(address(listener)); listener.collectTransferEvents(proof); vm.stopBroadcast(); } } ``` We run this script with the console command: ```bash forge script script/fdcExample/EVMTransaction.s.sol:InteractWithContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` --- ## Payment(Foundry) The `Payment` attestation type enables data collection about a transaction, classified as payment on the native chain. The currently supported chain are: `BTC`, `DOGE`, and `XRP`. You can learn more about it in the official [specification repo](/fdc/attestation-types/payment). We will now demonstrate how the FDC protocol can be used to collect the data of a given XRLP payment transaction. The transaction we will be observing has the id `2A3E7C7F6077B4D12207A9F063515EACE70FBBF3C55514CD8BD659D4AB721447`; this is an arbitrary transaction that we acquired from the XRPL testnet [explorer](https://testnet.xrpl.org/). The same procedure works for all supported sources, `BTC`, `DOGE`, and `XRP`. The source then requires only a slight modification; we will remind you of that when it comes up in the guide. In this guide, we will follow the steps outlined in the [FDC overview](/fdc/overview). Our implementation requires handling the FDC voting round finalization process. To manage this, we will create separate scripts in `script/fdcExample/Payment.s.sol` that handle different stages of the validation process: ```solidity title="script/fdcExample/Payment.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "Payment"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { ... } contract SubmitAttestationRequest is Script { ... } contract RetrieveDataAndProof is Script { ... } contract Deploy is Script { ... } ... ``` The names of included contracts mostly mirror the steps described in the [FDC guide](/fdc/overview). To bridge the separate executions of the scripts, we will save the relevant data of each script to a file in the `dirPath` folder. Each succeeding script will then read that file to load the data. ## Prepare request The JSON request to the verifier is the same form for all attestation types, but the values of the fields differ between them. It contains the following fields. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `Payment`, `requestBody` is a JSON containing the fields: - `transactionId`: id of the transaction; as `bytes32` - `inUtxo`: UTXO chains support multiple source addresses, so this is the index of the address considered, as `uint256`; for non-UTXO chains this should always be `0` - `utxo`: UTXO chains support multiple receiving addresses, so this is the index of the address considered, as `uint256`; for non-UTXO chains this should always be `0` ### Reference Documentation - [Payment Specification](/fdc/attestation-types/payment) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc#/) - API available for [DOGE](https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc#/) and [BTC](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/). ### Example Values - `transactionId`: the above address `9421cbb7f195df66d16703442a408261fa973514a0bd9dfc680f10eb3942d11f` - `inUtxo`: non-default `0` - `utxo`: non-default `0` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHexString`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) within the [example repository](https://github.com/flare-foundation/flare-foundry-starter), but they can also be defined locally in your contract or script. ```solidity title="script/fdcExample/Base.s.sol" function toHexString( bytes memory data ) public pure returns (string memory) { bytes memory alphabet = "0123456789abcdef"; bytes memory str = new bytes(2 + data.length * 2); str[0] = "0"; str[1] = "x"; for (uint i = 0; i < data.length; i++) { str[2 + i * 2] = alphabet[uint(uint8(data[i] >> 4))]; str[3 + i * 2] = alphabet[uint(uint8(data[i] & 0x0f))]; } return string(str); } ``` ```solidity title="script/fdcExample/Base.s.sol" function toUtf8HexString( string memory _string ) internal pure returns (string memory) { string memory encodedString = toHexString( abi.encodePacked(_string) ); uint256 stringLength = bytes(encodedString).length; require(stringLength <= 64, "String too long"); uint256 paddingLength = 64 - stringLength + 2; for (uint256 i = 0; i < paddingLength; i++) { encodedString = string.concat(encodedString, "0"); } return encodedString; } ``` We also define a helper function for formatting data into a JSON string. ```solidity title="script/fdcExample/Base.s.sol" function prepareAttestationRequest( string memory attestationType, string memory sourceId, string memory requestBody ) internal view returns (string[] memory, string memory) { // We read the API key from the .env file string memory apiKey = vm.envString("VERIFIER_API_KEY_TESTNET"); // Preparing headers string[] memory headers = prepareHeaders(apiKey); // Preparing body string memory body = prepareBody( attestationType, sourceId, requestBody ); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); console.log("body: %s\n", body); return (headers, body); } function prepareHeaders( string memory apiKey ) internal pure returns (string[] memory) { string[] memory headers = new string[](2); headers[0] = string.concat('"X-API-KEY": ', apiKey); headers[1] = '"Content-Type": "application/json"'; return headers; } function prepareBody( string memory attestationType, string memory sourceId, string memory body ) internal pure returns (string memory) { return string.concat( '{"attestationType": ', '"', attestationType, '"', ', "sourceId": ', '"', sourceId, '"', ', "requestBody": ', body, "}" ); } ``` In the example repository, these are once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) library file. Thus, the part of the script that prepares the verifier request looks like: ```solidity title="script/fdcExample/Payment.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "Payment"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { using Surl for *; // Setting request data string public transactionId = "2A3E7C7F6077B4D12207A9F063515EACE70FBBF3C55514CD8BD659D4AB721447"; string public inUtxo = "0"; string public utxo = "0"; string public baseSourceName = "xrp"; // Part of verifier URL string public sourceName = "testXRP"; // XRLP test chain ID function prepareRequestBody( string memory transactionId, string memory inUtxo, string memory utxo ) private pure returns (string memory) { return string.concat( '{"transactionId": "', transactionId, '", "inUtxo": "', inUtxo, '", "utxo": "', utxo, '"}' ); } function run() external { // Preparing request data string memory attestationType = Base.toUtf8HexString( attestationTypeName ); string memory sourceId = Base.toUtf8HexString(sourceName); string memory requestBody = prepareRequestBody( transactionId, inUtxo, utxo ); (string[] memory headers, string memory body) = prepareAttestationRequest(attestationType, sourceId, requestBody); ... } } ... ``` If you are accessing a different chain, replace the `baseSourceName` with an appropriate value, `doge` or `btc`. The code above differs slightly from the [starter example](https://github.com/flare-foundation/flare-foundry-starter). But, if we remove the ellipses `...` signifying missing code, we can still run the script. Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/Payment/BTCPaymentVerifierController_prepareRequest) to check what the response will be. We can run the script by calling the following commands in the console. ```bash source .env ``` ```bash forge script script/fdcExample/Payment.s.sol:PrepareAttestationRequest --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` The prerequisite for this is that the `.env` file is not missing the `PRIVATE KEY` and `COSTON2_RPC_URL` values. The script can also access other chains; that can be achieved by replacing the `--rpc-url` value with `COSTON_RPC_URL`, `FLARE_RPC_URL`, or `SONGBIRD_RPC_URL`. ## Post request to verifier. To post a request to a verifier server, we use the `surl` package. We place `using Surl for *;` at the start of our `PostRequest` contract, and then call its `post` method on the verifier URL. ```solidity title="script/fdcExample/Payment.s.sol" (, bytes memory data) = url.post(headers, body); ``` We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/` the path `verifier/btc_testnet4/Payment/prepareRequest`. We can do so dynamically with the following code. ```solidity title="script/fdcExample/Payment.s.sol" string memory baseUrl = "https://fdc-verifiers-testnet.flare.network/"; string memory url = string.concat( baseUrl, "verifier/", baseSourceName, "/", attestationTypeName, "/prepareRequest" ); console.log("url: %s", url); string memory requestBody = string.concat( '{"addressStr": "', addressStr, '"}' ); ``` Lastly, we parse the return data from the verifier server. Using the Foundry `parseJson` shortcode, and a custom struct `AttestationResponse`, we decode the returned data and extract from it the ABI encoded request. ```solidity title="script/fdcExample/Base.s.sol" function parseAttestationRequest( bytes memory data ) internal pure returns (AttestationResponse memory) { string memory dataString = string(data); bytes memory dataJson = vm.parseJson(dataString); AttestationResponse memory response = abi.decode( dataJson, (AttestationResponse) ); console.log("response status: %s\n", response.status); console.log("response abiEncodedRequest: "); console.logBytes(response.abiEncodedRequest); console.log("\n"); return response; } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 4164647265737356616c69646974790000000000000000000000000000000000 7465737442544300000000000000000000000000000000000000000000000000 7d2ef938d4ffd2392f588bf46563e07ab885b15fead91c1bb99b16f465b71a68 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("Payment")` - **Second line:** `toUtf8HexString("testETH")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `Payment.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
We write the `abiEncodedRequest` to a file (`data/Payment_abiEncodedRequest.txt`) to it in the next step. ```solidity title="script/fdcExample/Payment.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_abiEncodedRequest"), StringsBase.toHexString(response.abiEncodedRequest), true ); ``` ## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. Now, we submit the validated request to the blockchain using deployed smart contracts. ### Submit request The entire submission process requires only five key steps: ```solidity title="script/fdcExample/Base.s.sol" function submitAttestationRequest( AttestationResponse memory response ) internal { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( response.abiEncodedRequest ); console.log("request fee: %s\n", requestFee); vm.stopBroadcast(); vm.startBroadcast(deployerPrivateKey); IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); fdcHub.requestAttestation{value: requestFee}(response.abiEncodedRequest); vm.stopBroadcast(); } ``` ### Step-by-Step Breakdown 1. Load Private Key The private key is read from the `.env` file using Foundry's `envUint` function: ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); ``` 2. Obtain Request Fee We retrieve the required requestFee from the `FdcRequestFeeConfigurations` contract: ```solidity IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( response.abiEncodedRequest ); ``` This is done in a separate broadcast to ensure `requestFee` is available before submitting the request. 3. Access `FdcHub` Contract Using the `ContractRegistry` library (from `flare-periphery`), we fetch the `FdcHub` contract: ```solidity IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); ``` 4. Submit the Attestation Request We send the attestation request with the required fee: ```solidity fdcHub.requestAttestation{value: requestFee}(response.abiEncodedRequest); ``` 5. Calculate Voting Round Number To determine the voting round in which the attestation request is processed, we query the `FlareSystemsManager` contract: ```solidity // Calculating roundId IFlareSystemsManager flareSystemsManager = ContractRegistry .getFlareSystemsManager(); uint32 roundId = flareSystemsManager.getCurrentVotingEpochId(); console.log("roundId: %s\n", Strings.toString(roundId)); ``` This can be done within the existing broadcast or in a new one (as done in the demo repository for better code organization). ## Wait for response We wait for the round to finalize. This takes no more than 180 seconds. You can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. To learn more about how the FDC protocol works, check [here](/fdc/overview). ## Prepare proof request We prepare the proof request in a similar manner as in the step Prepare the request, by string concatenation. We import two new variables from the `.env` file; the URL of a verifier server and the corresponding API key. ```solidity title="script/fdcExample/Payment.s.sol" string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); ``` Also, by repeatedly using the Foundry shortcode `vm.readLine`, we read the data, saved to a file in the previous step, to variables. ```solidity title="script/fdcExample/Payment.s.sol" string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); ``` The code is as follows. ```solidity title="script/fdcExample/Payment.s.sol" contract RetrieveDataAndProof is Script { using Surl for *; function run() external { string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); string memory fileName = string.concat(attestationTypeName, ".txt"); string memory filePath = string.concat(dirPath, fileName); // We import the roundId and abiEncodedRequest from the first file string memory votingRoundId = vm.readLine(filePath); string memory requestBytes = vm.readLine(filePath); console.log("votingRoundId: %s\n", votingRoundId); console.log("requestBytes: %s\n", requestBytes); // Preparing the proof request string[] memory headers = prepareHeaders(apiKey); string memory body = string.concat( '{"votingRoundId":', votingRoundId, ',"requestBytes":"', requestBytes, '"}' ); console.log("body: %s\n", body); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); ... } } ``` ## Post proof request to DA Layer We post the proof request to a chosen DA Layer provider server also with the same code as we did in the previous step. ```solidity title="script/fdcExample/Payment.s.sol" string memory url = string.concat( daLayerUrl, // "api/v0/fdc/get-proof-round-id-bytes" "api/v1/fdc/proof-by-request-round-raw" ); console.log("url: %s\n", url); (, bytes memory data) = Base.postAttestationRequest(url, headers, body); ``` Parsing the returned data requires the definition of an auxiliary `struct`. {/* TODO rename after renaming */} ```solidity title="script/fdcExample/Base.s.sol" struct ParsableProof { bytes32 attestationType; bytes32[] proofs; bytes responseHex; } ``` The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x4164647265737356616c69646974790000000000000000000000000000000000`. The array `proofs` holds the Merkle proofs of our attestation request. Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IPayment.Response` struct. We retrieve this data as follows. ```solidity title="script/fdcExample/Payment.s.sol" bytes memory dataJson = parseData(data); ParsableProof memory proof = abi.decode(dataJson, (ParsableProof)); IPayment.Response memory proofResponse = abi.decode( proof.responseHex, (IPayment.Response) ); ```
An example complete proof response and decoded `IEVMTransaction.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 5061796d656e7400000000000000000000000000000000000000000000000000 7465737458525000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6c2d 00000000000000000000000000000000000000000000000000000000a019d806 2a3e7c7f6077b4d12207a9f063515eace70fbbf3c55514cd8bd659d4ab721447 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000048f822 0000000000000000000000000000000000000000000000000000000067ac9486 7f5b4967a9fbe9b447fed6d4e3699051516b6afe5f94db2e77ccf86470bfd74d a1475e9840d916c22f494c0dc25428d2affb5ae1f496efc82bbb59d46a336779 cd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82 cd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82 0000000000000000000000000000000000000000000000000000000005f5e10c 0000000000000000000000000000000000000000000000000000000005f5e10c 0000000000000000000000000000000000000000000000000000000005f5e100 0000000000000000000000000000000000000000000000000000000005f5e100 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000000', attestation_type: '0x5061796d656e7400000000000000000000000000000000000000000000000000', proof: [ '0xe1f98d39167eab17b2157c06efb80530b161d5eb15c439fc476e3242e30b3ac1', '0x23a8ffdb2cbaf0e2f3653923a159150f8d4c3ad5160f9e127cc9797ba233e6c2', '0xd756b90367b336e127f0759a1457825b4c2bf9011b71b56e15d9fcb7ff735ec8', '0xc881d1566868a986aef2bda47e9ab6dafeb8241bde5f5d53235837595829a5ea' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IEVMTransaction.Response` struct is: ```shell [ attestationType: '0x5061796d656e7400000000000000000000000000000000000000000000000000', sourceId: '0x7465737458525000000000000000000000000000000000000000000000000000', votingRound: '945197', lowestUsedTimestamp: '2686048262', requestBody: [ '0x2a3e7c7f6077b4d12207a9f063515eace70fbbf3c55514cd8bd659d4ab721447', '0', '0', transactionId: '0x2a3e7c7f6077b4d12207a9f063515eace70fbbf3c55514cd8bd659d4ab721447', inUtxo: '0', utxo: '0' ], responseBody: [ '4782114', '1739363462', '0x7f5b4967a9fbe9b447fed6d4e3699051516b6afe5f94db2e77ccf86470bfd74d', '0xa1475e9840d916c22f494c0dc25428d2affb5ae1f496efc82bbb59d46a336779', '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', '100000012', '100000012', '100000000', '100000000', '0x0000000000000000000000000000000000000000000000000000000000000000', true, '0', blockNumber: '4782114', blockTimestamp: '1739363462', sourceAddressHash: '0x7f5b4967a9fbe9b447fed6d4e3699051516b6afe5f94db2e77ccf86470bfd74d', sourceAddressesRoot: '0xa1475e9840d916c22f494c0dc25428d2affb5ae1f496efc82bbb59d46a336779', receivingAddressHash: '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', intendedReceivingAddressHash: '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', spentAmount: '100000012', intendedSpentAmount: '100000012', receivedAmount: '100000000', intendedReceivedAmount: '100000000', standardPaymentReference: '0x0000000000000000000000000000000000000000000000000000000000000000', oneToOne: true, status: '0' ] ] ```
## Verify proof Because every node holds a copy of the whole chain, storing data on the blockchain is expensive. For the sake of efficiency, FDC keeps only the Merkle proof onchain, while the data itself can be obtained from outside data providers. Per our request, they supply us with the specified data. That data then be encrypted, and its Merkle proof compared to the Merkle root stored onchain. If they match, the data can be trusted. This step is not strictly necessary; if we trust our data provider, we can skip this step. And in practice, we do just that. But it is crucial, that should we want to verify the data, we can do so. One way to do it is using the `FdcVerification` contract. We first store our data as an appropriate Solidity struct, namely `IPayment.Proof`. ```solidity title="script/fdcExample/Payment.s.sol" IPayment.Proof memory _proof = IPayment.Proof( proof.proofs, proofResponse ); ``` We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If the proof is valid, the function `verifyPayment` will return `true`, otherwise `false`. As before, we wrap the whole thing into a broadcast environment, using the `PRIVATE_KEY` variable from our `.env` file. ```solidity title="script/fdcExample/Payment.s.sol" uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); bool isValid = ContractRegistry .getFdcVerification() .verifyPayment(proof); console.log("proof is valid: %s\n", StringsBase.toString(isValid)); vm.stopBroadcast(); ``` In actuality, we will only verify the proof within a deployed contract, which we will define in the next step. What we will do here instead is, we will save the proof to a file so that it can be later loaded into a variable. The code that does this is as follows. ```solidity title="script/fdcExample/EVMTransaction.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_proof"), StringsBase.toHexString(abi.encode(_proof)), true ); ``` ## Use the data We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive data and proof of a Payment transaction, and store it into an array of special `Payment` structs. It will do so only if the transaction is valid. ```solidity title="src/fdcExample/Payment.sol" struct Payment { uint64 blockNumber; uint64 blockTimestamp; bytes32 sourceAddressHash; bytes32 receivingAddressHash; int256 spentAmount; bytes32 standardPaymentReference; uint8 status; } ``` First, we define an interface that the contract will inherit from. We do so, so that we may contact the contract later through a script. ```solidity title="src/fdcExample/Payment.sol" interface ITransferEventListener { function registerPayment( IPayment.Proof calldata _transaction ) external; } ``` The interface exposes the only function the script will call, `collectTransferEvents`. We now define the contract as follows. ```solidity title="src/fdcExample/Payment.sol" contract PaymentRegistry is IPaymentRegistry { Payment[] public verifiedPayments; function isPaymentProofValid( IPayment.Proof calldata transaction ) public view returns (bool) { // Use the library to get the verifier contract and verify that this transaction was proved by the FDC IFdcVerification fdc = ContractRegistry.getFdcVerification(); console.log("transaction: %s\n", FdcStrings.toJsonString(transaction)); // return true; return fdc.verifyPayment(transaction); } function registerPayment(IPayment.Proof calldata _transaction) external { // 1. FDC Logic // Check that this Payment has indeed been confirmed by the FDC require(isPaymentProofValid(_transaction), "Invalid transaction proof"); // 2. Business logic Payment memory provedPayment = Payment( _transaction.data.responseBody.blockNumber, _transaction.data.responseBody.blockTimestamp, _transaction.data.responseBody.sourceAddressHash, _transaction.data.responseBody.receivingAddressHash, _transaction.data.responseBody.spentAmount, _transaction.data.responseBody.standardPaymentReference, _transaction.data.responseBody.status ); verifiedPayments.push(provedPayment); } } ``` We deploy the contract through a simple script. The script creates a new `PaymentRegistry` contract, and writes its address to a file (`data/Payment_listenerAddress.txt`). ```solidity title="script/fdcExample/DeployContract.s.sol" contract DeployContract is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); PaymentRegistry paymentRegistry = new PaymentRegistry(); address _address = address(paymentRegistry); vm.stopBroadcast(); Base.writeToFile( dirPath, string.concat(attestationTypeName, "_address"), StringsBase.toHexString(abi.encodePacked(_address)), true ); } } ``` We deploy the contract with the following console command. ```bash forge script script/fdcExample/Payment.s.sol:DeployContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --broadcast --verify --verifier blockscout --verifier-url $COSTON2_EXPLORER_API --ffi ``` Lastly, we define a script that interacts with the above contract. It first reads the ABI encoded proof data, and the contract address, from files. Then, it connects to the above contract at the saved address (this is why we require the interface). With that, it is able to call the `registerPayment` method of the contract. ```solidity title="script/fdcExample/Payment.s.sol" contract InteractWithContract is Script { function run() external { string memory addressString = vm.readLine( string.concat(dirPath, attestationTypeName, "_address", ".txt") ); address _address = vm.parseAddress(addressString); string memory proofString = vm.readLine( string.concat(dirPath, attestationTypeName, "_proof", ".txt") ); bytes memory proofBytes = vm.parseBytes(proofString); IPayment.Proof memory proof = abi.decode(proofBytes, (IPayment.Proof)); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IPaymentRegistry registry = IPaymentRegistry(_address); registry.registerPayment(proof); vm.stopBroadcast(); } } ``` We run this script with the console command: ```bash forge script script/fdcExample/Payment.s.sol:InteractWithContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` --- ## Web2Json(Foundry) The `Web2Json` attestation type enables data collection from an arbitrary Web2 source, though the source has to be whitelisted by the Flare Network in advance. You can learn more about it in the official [specification repo](/fdc/attestation-types/web2-json). We will now demonstrate how the FDC protocol can be used to collect the data of a given [Star Wars API](https://swapi.dev/) request. It will be a `GET` request to the URL `https://swapi.info/api/peaople/3`. The same procedure works for all public APIs. In this guide, we will follow the steps outlined under User Workflow in the [FDC overview](/fdc/overview). Our implementation requires handling the FDC voting round finalization process. To manage this, we will create separate scripts in `script/fdcExample/Web2Json.s.sol` that handle different stages of the validation process: ```solidity title="script/fdcExample/Web2Json.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "Web2Json"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { ... } contract SubmitAttestationRequest is Script { ... } contract RetrieveDataAndProof is Script { ... } contract Deploy is Script { ... } ... ``` The names of included contracts mostly mirror the steps described in the [FDC guide](/fdc/overview). To bridge the separate executions of the scripts, we will save the relevant data of each script to a file in the `dirPath` folder. Each succeeding script will then read that file to load the data. :::info The code used is the guide is mostly take from the [Flare Foundry starter](https://github.com/flare-foundation/flare-foundry-starter) repository. ::: ## Prepare request The JSON request to the verifier is of the same form for all attestation types, but the values of the fields differ between them. It contains the following fields. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `Web2Json`, `requestBody` is a JSON containing the fields: - `url`: url of the data source; as `string` - `httpMethod`: one of `GET`, `POST`, `PUT`, `PATCH` and `DELETE` - `headers`: request headers as a stringified JSON; `{}` if no headers are required (defaults to `{"Content-Type": "application/json"}`) - `queryParams`: request query parameters as a stringified JSON; `{}` if no query parameters are required, - `body`: request body as a stringified JSON; `{}` if no body is required, - `postProcessJq`: JQ filter to postprocess the json data received from the URL; as `string` - `abiSignature`: ABI signature of the Solidity struct that will be used to decode the data; as `string` ### Reference Documentation - [Web2Json Specification](/fdc/attestation-types/json-api) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc#/) ### Example Values - `url`: the above address `https://swapi.info/api/people/3` - `httpMethod`: `GET` - `headers`: `{}`, which defaults to value `{"Content-Type": "application/json"}` - `queryParams`: `{}` - `body`: `{}` - `postProcessJq`: `{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}` - `abiSignature`: ```bash {"components": [ {"internalType": "string", "name": "name", "type": "string"}, {"internalType": "uint256", "name": "height", "type": "uint256"}, {"internalType": "uint256", "name": "mass", "type": "uint256"}, {"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"}, {"internalType": "uint256", "name": "uid", "type": "uint256"} ], "name": "task","type": "tuple"} ``` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHexString`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) within the [example repository](https://github.com/flare-foundation/flare-foundry-starter), but they can also be defined locally in your contract or script. ```solidity title="script/fdcExample/Base.s.sol" function toHexString( bytes memory data ) public pure returns (string memory) { bytes memory alphabet = "0123456789abcdef"; bytes memory str = new bytes(2 + data.length * 2); str[0] = "0"; str[1] = "x"; for (uint i = 0; i < data.length; i++) { str[2 + i * 2] = alphabet[uint(uint8(data[i] >> 4))]; str[3 + i * 2] = alphabet[uint(uint8(data[i] & 0x0f))]; } return string(str); } ``` ```solidity title="script/fdcExample/Base.s.sol" function toUtf8HexString( string memory _string ) internal pure returns (string memory) { string memory encodedString = toHexString( abi.encodePacked(_string) ); uint256 stringLength = bytes(encodedString).length; require(stringLength <= 64, "String too long"); uint256 paddingLength = 64 - stringLength + 2; for (uint256 i = 0; i < paddingLength; i++) { encodedString = string.concat(encodedString, "0"); } return encodedString; } ``` We also define a helper function for formatting data into a JSON string. ```solidity title="script/fdcExample/Base.s.sol" function prepareAttestationRequest( string memory attestationType, string memory sourceId, string memory requestBody ) internal view returns (string[] memory, string memory) { // We read the API key from the .env file string memory apiKey = vm.envString("VERIFIER_API_KEY_TESTNET"); // Preparing headers string[] memory headers = prepareHeaders(apiKey); // Preparing body string memory body = prepareBody( attestationType, sourceId, requestBody ); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); console.log("body: %s\n", body); return (headers, body); } function prepareHeaders( string memory apiKey ) internal pure returns (string[] memory) { string[] memory headers = new string[](2); headers[0] = string.concat('"X-API-KEY": ', apiKey); headers[1] = '"Content-Type": "application/json"'; return headers; } function prepareBody( string memory attestationType, string memory sourceId, string memory body ) internal pure returns (string memory) { return string.concat( '{"attestationType": ', '"', attestationType, '"', ', "sourceId": ', '"', sourceId, '"', ', "requestBody": ', body, "}" ); } ``` In the example repository, these are once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) library file. Thus, the part of the script that prepares the verifier request looks like: ```solidity title="script/fdcExample/Web2Json.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "Web2Json"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { using Surl for *; // Setting request data // string public apiUrl = "https://swapi.dev/api/people/3/"; string public apiUrl = "https://swapi.info/api/people/3"; string public httpMethod = "GET"; // Defaults to "Content-Type": "application/json" string public headers = '{\\"Content-Type\\":\\"text/plain\\"}'; string public queryParams = "{}"; string public body = "{}"; string public postProcessJq = '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split(\\"/\\") | .[-1] | tonumber)}'; string public abiSignature = '{\\"components\\": [{\\"internalType\\": \\"string\\", \\"name\\": \\"name\\", \\"type\\": \\"string\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"height\\", \\"type\\": \\"uint256\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"mass\\", \\"type\\": \\"uint256\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"numberOfFilms\\", \\"type\\": \\"uint256\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"uid\\", \\"type\\": \\"uint256\\"}],\\"name\\": \\"task\\",\\"type\\": \\"tuple\\"}'; string public sourceName = "PublicWeb2"; function prepareRequestBody( string memory url, string memory httpMethod, string memory headers, string memory queryParams, string memory body, string memory postProcessJq, string memory abiSignature ) private pure returns (string memory) { return string.concat( '{"url": "', url, '","httpMethod": "', httpMethod, '","headers": "', headers, '","queryParams": "', queryParams, '","body": "', body, '","postProcessJq": "', postProcessJq, '","abiSignature": "', abiSignature, '"}' ); } function run() external { // Preparing request data string memory attestationType = Base.toUtf8HexString( attestationTypeName ); string memory sourceId = Base.toUtf8HexString(sourceName); string memory requestBody = prepareRequestBody( apiUrl, httpMethod, headers, queryParams, body, postProcessJq, abiSignature ); (string[] memory headers, string memory body) = Base .prepareAttestationRequest(attestationType, sourceId, requestBody); // string memory baseUrl = "https://testnet-verifier-fdc-test.aflabs.org/"; string memory baseUrl = vm.envString("VERIFIER_URL_TESTNET"); string memory url = string.concat( baseUrl, "/verifier/web2/Web2Json/prepareRequest" ); console.log("url: %s", url); // Posting the attestation request (, bytes memory data) = url.post(headers, body); Base.AttestationResponse memory response = Base.parseAttestationRequest( data ); // Writing abiEncodedRequest to a file Base.writeToFile( dirPath, string.concat(attestationTypeName, "_abiEncodedRequest"), StringsBase.toHexString(response.abiEncodedRequest), true ); } } ... ``` The code above differs slightly from the [starter example](https://github.com/flare-foundation/flare-foundry-starter). But, if we remove the ellipses `...` signifying missing code, we can still run the script. Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc#) to check the response. We can run the script by calling the following commands in the console. ```bash source .env ``` ```bash forge script script/fdcExample/Web2Json.s.sol:PrepareAttestationRequest --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` The prerequisite for this is that the `.env` file is not missing the `PRIVATE KEY` and `COSTON2_RPC_URL` values. The script can also access other chains; that can be achieved by replacing the `--rpc-url` value with `COSTON_RPC_URL`, `FLARE_RPC_URL`, or `SONGBIRD_RPC_URL`. ## Post request to verifier To post a request to a verifier server, we use the `surl` package. We place `using Surl for *;` at the start of our `PostRequest` contract, and then call its `post` method on the verifier URL. ```solidity title="script/fdcExample/Web2Json.s.sol" (, bytes memory data) = url.post(headers, body); ``` We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/verifier/web2` the path `/JsonApi/prepareRequest`. We can do so dynamically with the following code. ```solidity title="script/fdcExample/Web2Json.s.sol" string memory baseUrl = "https://fdc-verifiers-testnet.flare.network/verifier/web2/"; string memory url = string.concat( baseUrl, "verifier/", baseSourceName, "/", attestationTypeName, "/prepareRequest" ); console.log("url: %s", url); string memory requestBody = string.concat( '{"addressStr": "', addressStr, '"}' ); ``` Lastly, we parse the return data from the verifier server. Using the Foundry `parseJson` shortcode, and a custom struct `AttestationResponse`, we decode the returned data and extract from it the ABI encoded request. ```solidity title="script/fdcExample/Base.s.sol" function parseAttestationRequest( bytes memory data ) internal pure returns (AttestationResponse memory) { string memory dataString = string(data); bytes memory dataJson = vm.parseJson(dataString); AttestationResponse memory response = abi.decode( dataJson, (AttestationResponse) ); console.log("response status: %s\n", response.status); console.log("response abiEncodedRequest: "); console.logBytes(response.abiEncodedRequest); console.log("\n"); return response; } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 576562324a736f6e000000000000000000000000000000000000000000000000 5075626c69635765623200000000000000000000000000000000000000000000 c69254586c046b1394616cbf98dc97ef115a492ff5dbcd9392cd775a102b2acc 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000000000e0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000160 00000000000000000000000000000000000000000000000000000000000001a0 00000000000000000000000000000000000000000000000000000000000001e0 0000000000000000000000000000000000000000000000000000000000000220 00000000000000000000000000000000000000000000000000000000000002c0 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e696e666f2f6170692f70656f706c652f3300 0000000000000000000000000000000000000000000000000000000000000003 4745540000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000001d 7b22436f6e74656e742d54797065223a22746578742f706c61696e227d000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d315d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("Web2Json")` - **Second line:** `toUtf8HexString("PublicWeb2")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `Web2Json.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
We write the `abiEncodedRequest` to a file (`data/Web2Json_abiEncodedRequest.txt`) to it in the next step. ```solidity title="script/fdcExample/Web2Json.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_abiEncodedRequest"), StringsBase.toHexString(response.abiEncodedRequest), true ); ``` ## Submit request to FDC This step transitions from off-chain request preparation to onchain interaction with the FDC protocol. Now, we submit the validated request to the blockchain using deployed smart contracts. ### Submit request The entire submission process requires only five key steps: ```solidity title="script/fdcExample/Base.s.sol" function submitAttestationRequest( bytes memory abiEncodedRequest ) internal { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( abiEncodedRequest ); console.log("request fee: %s\n", requestFee); vm.stopBroadcast(); vm.startBroadcast(deployerPrivateKey); IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); fdcHub.requestAttestation{value: requestFee}(abiEncodedRequest); vm.stopBroadcast(); } ``` ### Step-by-Step Breakdown 1. Load Private Key The private key is read from the `.env` file using Foundry's `envUint` function: ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); ``` 2. Obtain Request Fee We retrieve the required requestFee from the `FdcRequestFeeConfigurations` contract: ```solidity IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( abiEncodedRequest ); ``` This is done in a separate broadcast to ensure `requestFee` is available before submitting the request. 3. Access `FdcHub` Contract Using the `ContractRegistry` library (from `flare-periphery`), we fetch the `FdcHub` contract: ```solidity IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); ``` 4. Submit the Attestation Request We send the attestation request with the required fee: ```solidity fdcHub.requestAttestation{value: requestFee}(abiEncodedRequest); ``` 5. Calculate the Voting Round Number To determine the voting round in which the attestation request is processed, we query the `FlareSystemsManager` contract: ```solidity // Calculating roundId IFlareSystemsManager flareSystemsManager = ContractRegistry .getFlareSystemsManager(); uint32 roundId = flareSystemsManager.getCurrentVotingEpochId(); console.log("roundId: %s\n", Strings.toString(roundId)); ``` This can be done within the existing broadcast or in a new one (as done in the demo repository for better code organization). ## Wait for response We wait for the round to finalize. This takes no more than 180 seconds. You can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. To learn more about how the FDC protocol works, check [here](/fdc/overview). ## Prepare proof request We prepare the proof request in a similar manner as in the step Prepare the request, by string concatenation. We import two new variables from the `.env` file; the URL of a verifier server and the corresponding API key. ```solidity title="script/fdcExample/Web2Json.s.sol" string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); ``` Also, by repeatedly using the Foundry shortcode `vm.readLine`, we read the data, saved to a file in the previous step, to variables. ```solidity title="script/fdcExample/Web2Json.s.sol" string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); ``` The code is as follows. ```solidity title="script/fdcExample/Web2Json.s.sol" contract RetrieveDataAndProof is Script { using Surl for *; function run() external { string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); console.log("votingRoundId: %s\n", votingRoundId); console.log("requestBytes: %s\n", requestBytes); string[] memory headers = Base.prepareHeaders(apiKey); string memory body = string.concat( '{"votingRoundId":', votingRoundId, ',"requestBytes":"', requestBytes, '"}' ); console.log("body: %s\n", body); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); ... } } ``` ## Post proof request to DA Layer We post the proof request to a chosen DA Layer provider server also with the same code as we did in the previous step. ```solidity title="script/fdcExample/Web2Json.s.sol" string memory url = string.concat( daLayerUrl, // "api/v0/fdc/get-proof-round-id-bytes" "api/v1/fdc/proof-by-request-round-raw" ); console.log("url: %s\n", url); (, bytes memory data) = Base.postAttestationRequest(url, headers, body); ``` Parsing the returned data requires the definition of an auxiliary `struct`. {/* TODO rename after renaming */} ```solidity title="script/fdcExample/Base.s.sol" struct ParsableProof { bytes32 attestationType; bytes32[] proofs; bytes responseHex; } ``` The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x576562324a736f6e000000000000000000000000000000000000000000000000`. The array `proofs` holds the Merkle proofs of our attestation request. Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IWeb2Json.Response` struct. We retrieve this data as follows. ```solidity title="script/fdcExample/Web2Json.s.sol" bytes memory dataJson = parseData(data); ParsableProof memory proof = abi.decode(dataJson, (ParsableProof)); IWeb2Json.Response memory proofResponse = abi.decode( proof.responseHex, (IWeb2Json.Response) ); ```
An example complete proof response and decoded `IEVMTransaction.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 0000000000000000000000000000000000000000000000000000000000000020 576562324a736f6e000000000000000000000000000000000000000000000000 5075626c69635765623200000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000fa347 0000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000520 00000000000000000000000000000000000000000000000000000000000000e0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000160 00000000000000000000000000000000000000000000000000000000000001a0 00000000000000000000000000000000000000000000000000000000000001e0 0000000000000000000000000000000000000000000000000000000000000220 00000000000000000000000000000000000000000000000000000000000002c0 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e696e666f2f6170692f70656f706c652f3300 0000000000000000000000000000000000000000000000000000000000000003 4745540000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000001d 7b22436f6e74656e742d54797065223a22746578742f706c61696e227d000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d315d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000100 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000060 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000006 0000000000000000000000000000000000000000000000000000000000000003 0000000000000000000000000000000000000000000000000000000000000005 52322d4432000000000000000000000000000000000000000000000000000000', attestation_type: '0x576562324a736f6e000000000000000000000000000000000000000000000000', proof: [ '0x564e7596bf828952e3fbc21c889effced7c31a90fc0d5572a0b812faf87a1738', '0x738b3f104a0a56b2fbae32ea0e853a5b4529e0e4b915c89063e69bb61c03dde5' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself).
## Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. To verify address validity, we first format our data using the `IEVMTransaction.Proof` struct, which contains both the Merkle proof and the response data. ```solidity title="script/fdcExample/Web2Json.s.sol" IWeb2Json.Proof memory _proof = IWeb2Json.Proof( proof.proofs, proofResponse ); ``` We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If the proof is valid, the function `verifyWeb2Json` will return `true`, otherwise `false`. As before, we wrap the whole thing into a broadcast environment, using the `PRIVATE_KEY` variable from our `.env` file. ```solidity title="script/fdcExample/Web2Json.s.sol" uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); bool isValid = ContractRegistry .getFdcVerification() .verifyWeb2Json(proof); console.log("proof is valid: %s\n", StringsBase.toString(isValid)); vm.stopBroadcast(); ``` In actuality, we will only verify the proof within a deployed contract, which we will define in the next step. What we will do here instead is, we will save the proof to a file so that it can be later loaded into a variable. The code that does this is as follows. ```solidity title="script/fdcExample/EVMTransaction.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_proof"), StringsBase.toHexString(abi.encode(_proof)), true ); ``` ## Use the data We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive character data from the [Star Wars API](https://swapi.dev/), and store it in a `StarWarsCharacter` struct. It will do so only if the proof is valid. ```solidity title="src/fdcExample/Web2Json.sol" struct StarWarsCharacter { string name; uint256 numberOfMovies; uint256 apiUid; uint256 bmi; } ``` We will also need a `DataTransportObject` struct, that will allow us to decode the data. ```solidity title="src/fdcExample/Web2Json.sol" struct DataTransportObject { string name; uint256 height; uint256 mass; uint256 numberOfMovies; uint256 apiUid; } ``` First, we define an interface that the contract will inherit from. We do so, so that we may contact the contract later through a script. ```solidity title="src/fdcExample/Web2Json.sol" interface IStarWarsCharacterList { function addCharacter(IWeb2Json.Proof calldata data) external; function getAllCharacters() external view returns (StarWarsCharacter[] memory); } ``` The interface exposes the two functions that a user might call, `addCharacter` and `getAllCharacters`. We now define the contract as follows. ```solidity title="src/fdcExample/Web2Json.sol" contract StarWarsCharacterList { mapping(uint256 => StarWarsCharacter) public characters; uint256[] public characterIds; function isWeb2JsonProofValid( IWeb2Json.Proof calldata _proof ) private view returns (bool) { // Inline the check for now until we have an official contract deployed return ContractRegistry.getFdcVerification().verifyWeb2Json( _proof ); } function addCharacter(IWeb2Json.Proof calldata data) public { require(isWeb2JsonProofValid(data), "Invalid proof"); DataTransportObject memory dto = abi.decode( data.data.responseBody.abiEncodedData, (DataTransportObject) ); require(characters[dto.apiUid].apiUid == 0, "Character already exists"); StarWarsCharacter memory character = StarWarsCharacter({ name: dto.name, numberOfMovies: dto.numberOfMovies, apiUid: dto.apiUid, bmi: (dto.mass * 100 * 100) / (dto.height * dto.height) }); characters[dto.apiUid] = character; characterIds.push(dto.apiUid); } function getAllCharacters() public view returns (StarWarsCharacter[] memory) { StarWarsCharacter[] memory result = new StarWarsCharacter[]( characterIds.length ); for (uint256 i = 0; i < characterIds.length; i++) { result[i] = characters[characterIds[i]]; } return result; } function abiSignatureHack(DataTransportObject memory dto) public pure {} } ``` We deploy the contract through a simple script. The script creates a new `StarWarsCharacterList` contract and writes its address to a file (`data/Web2Json_listenerAddress.txt`). ```solidity title="script/fdcExample/DeployContract.s.sol" contract DeployContract is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); StarWarsCharacterList characterList = new StarWarsCharacterList(); address _address = address(characterList); vm.stopBroadcast(); Base.writeToFile( dirPath, string.concat(attestationTypeName, "_address"), StringsBase.toHexString(abi.encodePacked(_address)), true ); } } ``` We deploy the contract with the following console command. ```bash forge script script/fdcExample/Web2Json.s.sol:DeployContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --broadcast --verify --verifier blockscout --verifier-url $COSTON2_EXPLORER_API --ffi ``` Lastly, we define a script that interacts with the above contract. It first reads the ABI-encoded proof data, and the contract address, from files. Then, it connects to the above contract at the saved address (this is why we require the interface). With that, it can call the `registerWeb2Json` method of the contract. ```solidity title="script/fdcExample/Web2Json.s.sol" contract InteractWithContract is Script { function run() external { string memory addressString = vm.readLine( string.concat(dirPath, attestationTypeName, "_address", ".txt") ); address _address = vm.parseAddress(addressString); string memory proofString = vm.readLine( string.concat(dirPath, attestationTypeName, "_proof", ".txt") ); bytes memory proofBytes = vm.parseBytes(proofString); IWeb2Json.Proof memory proof = abi.decode(proofBytes, (IWeb2Json.Proof)); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IStarWarsCharacterList characterList = IStarWarsCharacterList(_address); characterList.addCharacter(proof); vm.stopBroadcast(); } } ``` We run this script with the console command: ```bash forge script script/fdcExample/Web2Json.s.sol:InteractWithContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` --- ## Proof of Reserves This is a guide on how to build a simple dApp using the [Flare Data Connector](/fdc/overview). It demonstrates how multiple attestation types, namely the [EVMTransaction](/fdc/attestation-types/evm-transaction) and [Web2Json](/fdc/attestation-types/web2-json), can be combined within the same app. The app that we will be building is called `proofOfReserves`, which enables onchain verification that a stablecoin's circulating supply is backed by sufficient offchain reserves. We will first describe what issue the app is addressing, and then provide a detailed walkthrough through its source code. All the code for this project is available on GitHub, in the [Flare Foundry Starter](https://github.com/flare-foundation/flare-foundry-starter) repository. ## The problem Stablecoins are cryptographic tokens designed to maintain a fixed value, typically pegged to a fiat currency like the US dollar. To maintain trust in the system, the issuing institution must hold sufficient reserves to back the tokens in circulation. The `proofOfReserves` application demonstrates how to verify that a stablecoin issuer maintains adequate offchain dollar reserves to cover all tokens in circulation across multiple blockchains. This verification creates transparency and helps prevent situations where more tokens exist than the backing reserves can support. Implementing this verification system presents three technical challenges: 1. **Accessing offchain data**: We need to query a Web2 API that reports the institution's official dollar reserves. 2. **Reading onchain state**: We need to access the total token supply data from various blockchain networks. 3. **Cross-chain data collection**: We need to aggregate token supply information across multiple chains. The [Flare Data Connector (FDC)](/fdc/overview) provides solutions for both accessing Web2 APIs through the [Web2Json](/fdc/attestation-types/web2-json) attestation type and collecting cross-chain data via the [EVMTransaction](/fdc/attestation-types/evm-transaction) attestation type. For reading onchain state, we deploy a dedicated contract that reads the token supply and emits this data as an event. This guide will walk through all the components needed to build the complete `proofOfReserves` verification system. ## Smart Contract Architecture For our proof of reserves implementation, we'll create three distinct smart contracts: 1. `MyStablecoin`: A custom ERC20 token for testing 2. `TokenStateReader`: A utility contract that reads and broadcasts token supply data 3. `ProofOfReserves`: The main verification contract that processes attestation proofs Note that in a production environment, we would typically only need two contracts - the main verification contract and a state reader. However, since this is a guide and we want flexibility to experiment with different token supply values, we'll also deploy our own stablecoin. #### Stablecoin Contract Let's start with the stablecoin implementation. This contract creates an ERC20-compatible token with additional functionality for burning tokens and controlled minting. ```solidity title="src/proofOfReserves/Token.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract MyStablecoin is ERC20, ERC20Burnable, Ownable, ERC20Permit { constructor( address recipient, address initialOwner ) ERC20("MyStablecoin", "MST") Ownable(initialOwner) ERC20Permit("MyStablecoin") { _mint(recipient, 666 * 10 ** decimals()); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } } ``` Because we are building our app around `@openzeppelin`'s ERC20 token, we can later replace the token with any such instance. This means that we can easily modify our app to work with an arbitrary contract that inherits the `ERC20`. #### TokenStateReader Contract The FDC's `EVMTransaction` attestation type works by verifying data from events. To get the `totalSupply` of our token on-chain, we deploy this simple contract. Its only function is to read the `totalSupply` of a given ERC20 token and emit it in an event, making the state readable by the FDC. ```solidity title="src/proofOfReserves/TokenStateReader.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract TokenStateReader { event TotalTokenSupply(address tokenAddress, uint256 totalSupply); function broadcastTokenSupply(ERC20 token) external returns (uint256) { uint256 supply = token.totalSupply(); emit TotalTokenSupply(address(token), supply); return supply; } } ``` #### ProofOfReserves Contract The final component in our implementation is the `ProofOfReserves` contract, which performs the actual verification of reserve adequacy. This contract evaluates whether the claimed dollar reserves are sufficient to back all tokens in circulation across different blockchains. The core functionality is contained in the `verifyReserves` function, which accepts two parameters: - An `IWeb2Json.Proof` struct containing attested data from the Web2 API about dollar reserves - An array of `IEVMTransaction.Proof` structs containing attested data about token supplies from various blockchains The function aggregates the total token supply from all chains and compares it against the claimed reserves. If sufficient reserves exist (i.e., if the total token supply is less than or equal to the claimed reserves), the function returns `true`; otherwise, it returns `false`. ```solidity title="src/proofOfReserves/ProofOfReserves.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; // ... helper structs and events contract ProofOfReserves is Ownable { // ... state variables and events mapping(address => address) public tokenStateReaders; constructor() Ownable(msg.sender) {} function updateAddress(address readerAddress, address tokenAddress) public onlyOwner { tokenStateReaders[readerAddress] = tokenAddress; } // Two events and values for debug purposes event GoodPair(address reader, address token, uint256 totalSupply); event BadPair(address reader, address token, uint256 totalSupply); uint256 public debugTokenReserves; uint256 public debugClaimedReserves; function abiSignatureHack(DataTransportObject calldata dto) public pure {} function verifyReserves( IWeb2Json.Proof calldata jsonProof, IEVMTransaction.Proof[] calldata transactionProofs ) external returns (bool) { uint256 claimedReserves = readReserves(jsonProof); uint256 totalTokenSupply = 0; for (uint256 i = 0; i < transactionProofs.length; i++) { totalTokenSupply += readReserves(transactionProofs[i]); } debugTokenReserves = totalTokenSupply; return totalTokenSupply <= (claimedReserves * 1 ether); } function readReserves(IWeb2Json.Proof calldata proof) private returns (uint256) { require(isValidProof(proof), "Invalid json proof"); DataTransportObject memory data = abi.decode(proof.data.responseBody.abiEncodedData, (DataTransportObject)); debugClaimedReserves = data.reserves; return data.reserves; } function readReserves(IEVMTransaction.Proof calldata proof) private returns (uint256) { require(isValidProof(proof), "Invalid transaction proof"); uint256 totalSupply = 0; for (uint256 i = 0; i < proof.data.responseBody.events.length; i++) { IEVMTransaction.Event memory _event = proof.data.responseBody.events[i]; address readerAddress = _event.emitterAddress; (address tokenAddress, uint256 supply) = abi.decode(_event.data, (address, uint256)); if (tokenStateReaders[readerAddress] == tokenAddress) { totalSupply += supply; emit GoodPair(readerAddress, tokenAddress, supply); } else { emit BadPair(readerAddress, tokenAddress, supply); } } return totalSupply; } function isValidProof(IWeb2Json.Proof calldata proof) private view returns (bool) { return ContractRegistry.getFdcVerification().verifyWeb2Json(proof); } function isValidProof(IEVMTransaction.Proof calldata proof) private view returns (bool) { return ContractRegistry.getFdcVerification().verifyEVMTransaction(proof); } } ``` --- ### Process Overview This guide demonstrates deployment on Flare's Coston and Coston2 testnets, but the same approach can be adapted for any EVM chain. The complete process follows these sequential steps: 1. Deploy and verify the `MyStablecoin` contract on both Coston and Coston2 chains 2. Deploy and verify the `TokenStateReader` contract on both Coston and Coston2 chains 3. Deploy and verify the `ProofOfReserves` contract on Coston2 chain only 4. Save `MyStablecoin`, `TokenStateReader`, and `ProofOfReserves` addresses to `.txt` files under `data/proofOfReserves/` 5. Call the `broadcastTokenSupply` function of both `TokenStateReader` contracts with the corresponding `MyStablecoin` addresses 6. Save transaction hashes of both function calls to `.txt` files under `data/proofOfReserves/` 7. Request attestation from the [FDC](/fdc/overview), and call `verifyReserves` function of the `ProofOfReserves` with the received data Throughout this guide, we'll provide separate scripts for each step above, with filenames that clearly indicate their purpose. :::warning While we deploy stablecoin and reader contracts on both chains, the `ProofOfReserves` contract is deployed only on the Coston2 chain, which serves as our verification hub. ::: ## Scripts The first three scripts each deploy and verify one of the contracts defined in the first part of the guide, ie. `MyStablecoin`, `TokenStateReader`, and `ProofOfReserves`. They are more or less the same script, the only real difference being the contracts deployed, and the arguments that are passed to their constructor. The `Deploy` script deploys and verifies the `MyStablecoin` contract on the Coston and Coston2 chain. :::note The intermediate `.txt` filenames shown below (`_token.txt`, `_proofOfReserves.txt`, `_EVMTransaction_Coston_request.txt`, `_Web2Json_roundId.txt`, etc.) describe the **intended**, consistent naming. The current `flare-foundry-starter` is being aligned to this scheme in a follow-up PR — until then, the files actually written and read by the script may differ in their leading-underscore and separator placement. If you run the starter and a step can't find the expected file, double-check the filename hyphenation in the failing script. ::: ### Step 1: Deploy Contracts The `Deploy` script handles the initial setup, deploying all necessary contracts to their respective chains and saving their addresses to configuration files for later scripts to use. ```solidity title="script/ProofOfReserves.s.sol" contract Deploy is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address owner = vm.addr(deployerPrivateKey); uint256 chainId = block.chainid; vm.createDir(dirPath, true); vm.startBroadcast(deployerPrivateKey); // Deploy contracts MyStablecoin token = new MyStablecoin(owner, owner); TokenStateReader reader = new TokenStateReader(); // Write addresses to chain-specific files string memory tokenPath = string.concat(dirPath, "_token_", Strings.toString(chainId), ".txt"); string memory readerPath = string.concat(dirPath, "_reader_", Strings.toString(chainId), ".txt"); vm.writeFile(tokenPath, vm.toString(address(token))); vm.writeFile(readerPath, vm.toString(address(reader))); // Deploy the main contract only to Coston2 if (chainId == 114) { // Coston2 ProofOfReserves proofOfReserves = new ProofOfReserves(); string memory porPath = string.concat(dirPath, "_proofOfReserves_", Strings.toString(chainId), ".txt"); vm.writeFile(porPath, vm.toString(address(proofOfReserves))); console.log("ProofOfReserves deployed to:", address(proofOfReserves)); } vm.stopBroadcast(); console.log("--- Deployment Results for Chain ID:", chainId, "---"); console.log("MyStablecoin deployed to:", address(token)); console.log("TokenStateReader deployed to:", address(reader)); } } ``` Run this script on both Coston and Coston2: ```bash forge script script/ProofOfReserves.s.sol:Deploy --rpc-url $COSTON_RPC_URL --broadcast forge script script/ProofOfReserves.s.sol:Deploy --rpc-url $COSTON2_RPC_URL --broadcast ``` ### Step 2: Activate State Readers The `ActivateReaders` script calls the `broadcastTokenSupply` function on the `TokenStateReader` contracts on both chains. This creates a transaction with a `TotalTokenSupply` event. The script then reads the transaction hash from the Foundry broadcast receipt and saves it to a file. This hash is the input for the `EVMTransaction` attestation. ```solidity title="script/ProofOfReserves.s.sol" contract ActivateReaders is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); uint256 chainId = block.chainid; address tokenAddress = vm.parseAddress(vm.readFile(string.concat(dirPath, "_token_", Strings.toString(chainId), ".txt"))); address readerAddress = vm.parseAddress(vm.readFile(string.concat(dirPath, "_reader_", Strings.toString(chainId), ".txt"))); TokenStateReader reader = TokenStateReader(readerAddress); MyStablecoin token = MyStablecoin(tokenAddress); vm.startBroadcast(deployerPrivateKey); reader.broadcastTokenSupply(token); vm.stopBroadcast(); // Read the transaction hash from the broadcast receipt string memory receiptPath = string(abi.encodePacked("broadcast/ProofOfReserves.s.sol/", vm.toString(chainId), "/run-latest.json")); string memory receiptJson = vm.readFile(receiptPath); string memory txHash = receiptJson.readString(".transactions[0].transactionHash"); string memory txHashPath = string.concat(dirPath, "_txHash_", Strings.toString(chainId), ".txt"); vm.writeFile(txHashPath, txHash); console.log("Reader activated on chain:", chainId, "with txHash:", txHash); } } ``` Run this script on both chains to generate the necessary on-chain events: ```bash forge script script/ProofOfReserves.s.sol:ActivateReaders --rpc-url $COSTON_RPC_URL --broadcast forge script script/ProofOfReserves.s.sol:ActivateReaders --rpc-url $COSTON2_RPC_URL --broadcast ``` ### Step 3: Prepare Attestation Requests This script reads the transaction hashes from the files and prepares all the necessary FDC requests. This is an off-chain step that uses `--ffi` to call the verifier APIs and construct the ABI-encoded request bytes. ```solidity title="script/ProofOfReserves.s.sol" contract PrepareRequests is Script { function run() external { string memory txHashCoston = vm.readFile(string.concat(dirPath, "_txHash_16.txt")); string memory txHashCoston2 = vm.readFile(string.concat(dirPath, "_txHash_114.txt")); // Prepare Web2 reserve data request bytes memory web2JsonRequest = prepareWeb2JsonRequest(); FdcBase.writeToFile(dirPath, "_Web2Json_request.txt", StringsBase.toHexString(web2JsonRequest), true); // Prepare EVM transaction data requests for both chains bytes memory evmCostonRequest = prepareEvmTransactionRequest("testSGB", "sgb", txHashCoston); FdcBase.writeToFile(dirPath, "_EVMTransaction_Coston_request.txt", StringsBase.toHexString(evmCostonRequest), true); bytes memory evmCoston2Request = prepareEvmTransactionRequest("testFLR", "flr", txHashCoston2); FdcBase.writeToFile(dirPath, "_EVMTransaction_Coston2_request.txt", StringsBase.toHexString(evmCoston2Request), true); } // ... helper functions to call verifier APIs ... } ``` Run the script to prepare all requests: ```bash forge script script/ProofOfReserves.s.sol:PrepareRequests --rpc-url $COSTON2_RPC_URL --ffi ``` ### Step 4: Submit Requests to FDC This script reads the prepared request bytes from the files and submits them to the `FdcHub` contract, initiating the attestation process for each piece of data. The `votingRoundId` for each request is saved for proof retrieval. ```solidity title="script/ProofOfReserves.s.sol" contract SubmitRequests is Script { function run() external { _submitRequest("_Web2Json"); _submitRequest("_EVMTransaction_Coston"); _submitRequest("_EVMTransaction_Coston2"); } function _submitRequest(string memory attestationType) private { // ... reads request file, submits, and writes roundId file ... } } ``` Run the script to submit all requests to the FDC: ```bash forge script script/ProofOfReserves.s.sol:SubmitRequests --rpc-url $COSTON2_RPC_URL --broadcast ``` ### Step 5: Retrieve Proofs After waiting for the voting rounds to finalize (max. 180 seconds), this script queries the Data Availability layer for the attestation proofs for all three requests and saves them to files. ```solidity title="script/ProofOfReserves.s.sol" contract RetrieveProofs is Script { function run() external { // ... retrieves and saves Web2Json proof ... // ... retrieves and saves Coston EVM proof ... // ... retrieves and saves Coston2 EVM proof ... } // ... helper functions to parse proof data ... } ``` Run the script to fetch the finalized proofs: ```bash forge script script/ProofOfReserves.s.sol:RetrieveProofs --rpc-url $COSTON2_RPC_URL --ffi ``` ### Step 6: Verify Reserves This final script reads the three saved proofs from their files, updates the `ProofOfReserves` contract with the correct reader-to-token mappings, and calls the `verifyReserves` function with all the proofs. The contract performs the on-chain verification, and the script logs the final result. ```solidity title="script/ProofOfReserves.s.sol" contract VerifyReserves is Script { function run() external { IWeb2Json.Proof memory web2Proof = abi.decode(/* ... */); IEVMTransaction.Proof memory evmCostonProof = abi.decode(/* ... */); IEVMTransaction.Proof memory evmCoston2Proof = abi.decode(/* ... */); address proofOfReservesAddress = vm.parseAddress(/* ... */); // ... read other addresses ProofOfReserves por = ProofOfReserves(payable(proofOfReservesAddress)); IEVMTransaction.Proof[] memory evmProofs = new IEVMTransaction.Proof[](2); evmProofs[0] = evmCostonProof; evmProofs[1] = evmCoston2Proof; vm.startBroadcast(vm.envUint("PRIVATE_KEY")); por.updateAddress(readerCostonAddress, tokenCostonAddress); por.updateAddress(readerCoston2Address, tokenCoston2Address); bool success = por.verifyReserves(web2Proof, evmProofs); vm.stopBroadcast(); console.log("\n--- VERIFICATION COMPLETE ---"); console.log("Sufficient Reserves Check Passed:", success); } } ``` Run the script to execute the final verification: ```bash forge script script/ProofOfReserves.s.sol:VerifyReserves --rpc-url $COSTON2_RPC_URL --broadcast ``` --- ## Weather Insurance In this guide, we will examine an example of a simple insurance dApp that uses the FDC's [Web2Json](/fdc/guides/foundry/web2-json) attestation type. The dApp will allow users to create insurance policies for temperatures at specific coordinates falling below a specified threshold. Other users will be able to claim those policies, and the policies will be settled automatically by the contract. All the code described in this guide is available on GitHub in the [Flare Foundry starter repository](https://github.com/flare-foundation/flare-foundry-starter). ### The Process of Using the dApp To start with, let us describe the requirements for our Weather Insurance dApp. We will do so by laying out how we expect the users to interact with the main contract. There will be three actors involved in the process: - **Policyholder**: the entity possessing assets that they want to insure against unfavorable weather conditions. - **Insurer**: the entity willing to take on the risk in exchange for a premium. - **Contract**: the smart contract that will handle the logic of the exchange. With that, we can describe the process of using the Weather Insurance dApp. 1. The policyholder creates a policy, specifying: - Location of insured asset (latitude, longitude) - Policy duration (start and expiration timestamp) - Minimum temperature threshold constituting the loss - Premium amount - Loss coverage amount They also deposit the premium to the contract that will resolve the policy. 2. The insurer accepts the policy and deposits the loss coverage amount to the contract. The contract pays out the deposited premium to the insurer. If no insurer has accepted the policy by the time it comes into effect (before its start timestamp), the policy can be retired. 3. The policy is resolved. This happens in three ways: - The policy has expired because no insurer accepted it in the allotted time. The contract returns the premium to the policyholder. - An insurer has accepted the policy, and proof has been provided to the contract, demonstrating that a loss has occurred (in this case, that the temperature at the specified location fell below the agreed-upon threshold). The contract pays out the deposited loss coverage amount to the policyholder. - An insurer has accepted the policy, and the expiration timestamp has been reached without valid proof having been provided. The contract returns the loss coverage deposit to the insurer. With that we can now focus on the technical aspects of the procedure described above, starting with the main smart contract. ### The Contract The contract that we will define will be called `MinTempAgency`. This name coincides with the field name in the response on a call to the [OpenWeatherMap API](https://openweathermap.org/api), which we will be using to acquire weather data. We will use the [Flare Data Connector](/fdc/overview) to collect the weather data so, in keeping with the [Web2Json guide](/fdc/guides/foundry/web2-json), we start by defining a `DataTransportObject` which will determine how the FDC should encode the data. ```solidity title="src/weatherInsurance/MinTempAgency.sol" struct DataTransportObject { int256 latitude; int256 longitude; string description; int256 temperature; int256 minTemp; uint256 windSpeed; uint256 windDeg; } ``` In its response, the API returns the latitude and longitude of the closest weather station to the desired coordinates. The policyholder is thus responsible for providing these, otherwise, they will not be able to prove that a loss has occurred. We store the weather station's latitude and longitude in the policy so that we can later confirm that the proof pertains to the correct location. The policy also includes a `minTemp` field, which will serve as the criterion for a loss. We save additional values to the policy, which may serve as an inspiration for defining additional policy types. At the top of the contract, we define a `Policy` struct, which will represent a policy once it has been registered. In addition to the values originating from the `DataTransportObject`, we declare the following fields: - **holder**: the address of the policyholder that created the policy - **premium**: the deposited premium for the policy - **coverage**: the expected loss coverage amount if the policy is accepted - **status**: a `PolicyStatus` enum value describing the state of the policy, with either of the following values: - **Unclaimed**: the policyholder has created the policy, but no insurer has accepted it yet - **Open**: an insurer has accepted the policy, but it has not been resolved yet - **Settled**: the policy has been resolved - **id**: a unique value identifying the policy The contract will save the policies to an array `registeredPolicies`. When a policy is accepted, the contract will save the insurer's address to the mapping `insurers`. We also define several events that will be emitted at different stages of a policy's lifetime. ```solidity title="src/weatherInsurance/MinTempAgency.sol" contract MinTempAgency { Policy[] public registeredPolicies; mapping(uint256 => address) public insurers; enum PolicyStatus { Unclaimed, Open, Settled } struct Policy { address holder; int256 latitude; int256 longitude; uint256 startTimestamp; uint256 expirationTimestamp; int256 minTempThreshold; uint256 premium; uint256 coverage; PolicyStatus status; uint256 id; } event PolicyCreated(uint256 id); event PolicyClaimed(uint256 id); event PolicySettled(uint256 id); event PolicyExpired(uint256 id); event PolicyRetired(uint256 id); // ... } ``` The function for policy creation requires a premium to be paid to the contract. The premium is thus not one of the parameters of this function. If no premium has been deposited, the function reverts. The function also ensures that the expiration timestamp is greater than the start timestamp. If these two checks are passed, a new `Policy` struct is created and added to the array of registered policies. A `PolicyCreated` event is emitted, with the policy ID as value. ```solidity title="src/weatherInsurance/MinTempAgency.sol" function createPolicy( int256 latitude, int256 longitude, uint256 startTimestamp, uint256 expirationTimestamp, int256 minTempThreshold, uint256 coverage ) public payable { require(msg.value > 0, "No premium paid"); require(startTimestamp < expirationTimestamp, "Value of startTimestamp larger than expirationTimestamp"); Policy memory newPolicy = Policy({ holder: msg.sender, latitude: latitude, longitude: longitude, startTimestamp: startTimestamp, expirationTimestamp: expirationTimestamp, minTempThreshold: minTempThreshold, premium: msg.value, coverage: coverage, status: PolicyStatus.Unclaimed, id: registeredPolicies.length }); registeredPolicies.push(newPolicy); emit PolicyCreated(newPolicy.id); } ``` :::info Because Solidity does not support floating-point numbers, we will store the fractional values as their 106 multiple. So, instead of Celsius, we will use micro-Celsius, instead of degrees for latitude and longitude micro-degrees, and so on. ::: The `claimPolicy` function first retrieves the policy with the given ID. It checks that the policy is still `Unclaimed`. If the policy's start timestamp has already passed, the function silently routes through `retireUnclaimedPolicy` (without reverting), so an insurer can never accidentally claim a stale policy. Just like the premium, the coverage value is also not a parameter but the amount paid to the contract. The function checks that it has received a sufficient amount before continuing. If all checks have passed, the policy's status is set to `Open`. The `registeredPolicies` array is updated, and the insurer added to the mapping `insurers`. Lastly, the premium is paid to the insurer, and a `PolicyClaimed` event is emitted. ```solidity title="src/weatherInsurance/MinTempAgency.sol" function claimPolicy(uint256 id) public payable { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Unclaimed, "Policy already claimed"); if (block.timestamp > policy.startTimestamp) { retireUnclaimedPolicy(id); } require(msg.value >= policy.coverage, "Insufficient coverage paid"); policy.status = PolicyStatus.Open; registeredPolicies[id] = policy; insurers[id] = msg.sender; payable(msg.sender).transfer(policy.premium); emit PolicyClaimed(id); } ``` :::danger Any coin transfer must be performed only after the state has been updated. Otherwise, the contract is open for a reentrancy attack. ::: The code that resolves a policy has been extended to provide a better description of conditions that revert the function. The function first collects the policy with the provided ID, and checks that its status is `Open`. Then, it validates the provided proof with the `isWeb2JsonProofValid` helper function. If the proof is valid, the `resolvePolicy` function decodes the enclosed data to a `DataTransportObject` struct. Several checks follow. The first two ensure that we are currently within the time interval, described by the policy. We assume, that the data relates to the current weather conditions. For that reason, the function compares the timestamp of the current block to the policy's start and expiration timestamp. If the current timestamp is smaller than the start timestamp, the function reverts. If it exceeds the expiration timestamp, we expire the policy. Next, the function compares the coordinates provided by the proof to those of the policy, requiring they match. Lastly, it checks that the condition for a loss has been met; namely, that the minimum temperature in the proof falls below the threshold value set by the policy. Finally, if all checks have passed, the function marks the policy as `Settled`, and transfers the coverage amount to the policyholder. A `PolicySettled` event is emitted. ```solidity title="src/weatherInsurance/MinTempAgency.sol" function resolvePolicy(uint256 id, IWeb2Json.Proof calldata proof) public { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Open, "Policy not open"); require(isJsonApiProofValid(proof), "Invalid proof"); DataTransportObject memory dto = abi.decode(proof.data.responseBody.abiEncodedData, (DataTransportObject)); require( block.timestamp >= policy.startTimestamp, string.concat( "Policy not yet in effect: ", Strings.toString(block.timestamp), " vs. ", Strings.toString(policy.startTimestamp) ) ); if (block.timestamp > policy.expirationTimestamp) { expirePolicy(id); return; } require( dto.latitude == policy.latitude && dto.longitude == policy.longitude, string.concat( "Invalid coordinates: ", Strings.toStringSigned(dto.latitude), ", ", Strings.toStringSigned(dto.longitude), " vs. ", Strings.toStringSigned(policy.latitude), ", ", Strings.toStringSigned(policy.longitude) ) ); require( dto.minTemp <= policy.minTempThreshold, string.concat( "Minimum temperature not met: ", Strings.toStringSigned(dto.minTemp), " vs. ", Strings.toStringSigned(policy.minTempThreshold) ) ); policy.status = PolicyStatus.Settled; registeredPolicies[id] = policy; payable(policy.holder).transfer(policy.coverage); emit PolicySettled(id); } ``` The `IWeb2Json.Proof` is validated as demonstrated in the [Web2Json guide](/fdc/guides/foundry/web2-json). ```solidity title="src/weatherInsurance/MinTempAgency.sol" function isJsonApiProofValid(IWeb2Json.Proof calldata _proof) private view returns (bool) { return ContractRegistry.getFdcVerification().verifyWeb2Json(_proof); } ``` Despite being called from within the function that resolves a policy, the `expirePolicy` function can serve as a standalone function. For that reason, it performs the two checks for the policy's expiration again; it ensures its status is `Open`, and that the timestamp of the current block is greater than the expiration timestamp of the policy. If the checks pass, the policy is marked as `Settled`, and the coverage is returned to the insurer. A `PolicyExpired` event is emitted. ```solidity title="src/weatherInsurance/MinTempAgency.sol" function expirePolicy(uint256 id) public { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Open, "Policy not open"); require(block.timestamp > policy.expirationTimestamp, "Policy not yet expired"); policy.status = PolicyStatus.Settled; registeredPolicies[id] = policy; payable(insurers[id]).transfer(policy.coverage); emit PolicyExpired(id); } ``` The last of the non-helper functions allows us to retire unclaimed policies. If the policy's status is `Unclaimed`, and the timestamp of the current block exceeds the policy's start timestamp, the policy is marked as `Settled`, and its premium is returned to the policyholder. A `PolicyRetired` event is emitted. ```solidity title="src/weatherInsurance/MinTempAgency.sol" function retireUnclaimedPolicy(uint256 id) public { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Unclaimed, "Policy not unclaimed"); require(block.timestamp > policy.startTimestamp, "Policy not yet expired"); policy.status = PolicyStatus.Settled; registeredPolicies[id] = policy; payable(policy.holder).transfer(policy.premium); emit PolicyRetired(id); } ``` The remaining functions serve a utility purpose. The `getPolicy` function allows us to query for a specific policy, while `getInsurer` function allows us to query policy insurers. ```solidity title="src/weatherInsurance/MinTempAgency.sol" function getPolicy(uint256 id) public view returns (Policy memory) { return registeredPolicies[id]; } function getInsurer(uint256 id) public view returns (address) { return insurers[id]; } ``` ### The Scripts The following scripts reflect the process described at the start of this guide. Most of them are straightforward, performing a single function call. The first script deploys the `MinTempAgency` contract and saves its address to a file in the `data/weatherInsurance/` directory for other scripts to use. ```solidity title="script/MinTemp.s.sol" contract DeployAgency is Script { string private constant dirPath = "data/weatherInsurance/"; function run() external { vm.createDir(dirPath, true); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); MinTempAgency agency = new MinTempAgency(); vm.stopBroadcast(); string memory filePath = string.concat(dirPath, "_agencyAddress.txt"); vm.writeFile(filePath, vm.toString(address(agency))); console.log("MinTempAgency deployed to:", address(agency)); } } ``` **Run the script:** ```bash forge script script/MinTemp.s.sol:DeployAgency --rpc-url $COSTON2_RPC_URL --broadcast --verify --verifier blockscout --verifier-url $COSTON2_EXPLORER_API ``` Next, we will create a new policy with the following parameters: - latitude: `46.419402` - longitude: `15.587079` - start timestamp: 180 seconds from now - expiration timestamp: an hour from now (3600 seconds duration) - minimum temperature threshold: 10 degrees Celsius (encoded as `10 * 1e6` micro-Celsius) - premium: `0.01 ether` - coverage: `0.1 ether` :::note We have set the minimum temperature threshold high enough that the policy will always be resolved successfully. ::: Because the response of the OpenWeatherMap API includes not the provided coordinates, but those of the nearest weather station, we will first replace our latitude and longitude with those. This will ensure that the coordinates of the created policy match the ones in the proof data. Without this step, we could never prove that a loss has occurred. We find the correct coordinates by making a GET request to the same URL that will be provided to the FDC. To do this in a Foundry script, we use the `ffi` cheatcode, which can execute arbitrary shell commands. To use the API, we need to provide it with an API key, which is available on a free account. We read the API key from the `.env` file. Also, we explicitly state that we are using metric units, although this is the default. The script calls the API via `curl`, parses the JSON response to get the exact coordinates, scales them by 106 to avoid floating-point values, and finally calls `createPolicy`. ```solidity title="script/MinTemp.s.sol" contract CreatePolicy is Script { function run() external { // ... (Helper function to get deployed agency address) string memory lat = "46.419402"; string memory lon = "15.587079"; string memory apiKey = vm.envString("OPEN_WEATHER_API_KEY"); string memory units = "metric"; string memory url = string.concat("https://api.openweathermap.org/data/2.5/weather?lat=", lat, "&lon=", lon, "&appid=", apiKey, "&units=", units); string[] memory inputs = new string[](3); inputs[0] = "curl"; inputs[1] = "-s"; inputs[2] = url; bytes memory jsonResponseBytes = vm.ffi(inputs); string memory jsonResponse = string(jsonResponseBytes); string memory latString = vm.parseJsonString(jsonResponse, ".coord.lat"); string memory lonString = vm.parseJsonString(jsonResponse, ".coord.lon"); int256 actualLatitude = FdcBase.stringToScaledInt(latString, 6); int256 actualLongitude = FdcBase.stringToScaledInt(lonString, 6); uint256 startTimestamp = block.timestamp + 180; uint256 expirationTimestamp = startTimestamp + 1 hours; int256 minTempThreshold = 10 * 1e6; uint256 premium = 0.01 ether; uint256 coverage = 0.1 ether; uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); agency.createPolicy{value: premium}(actualLatitude, actualLongitude, startTimestamp, expirationTimestamp, minTempThreshold, coverage); vm.stopBroadcast(); } // ... } ``` **Run the script:** ```bash forge script script/MinTemp.s.sol:CreatePolicy --rpc-url $COSTON2_RPC_URL --broadcast --ffi ``` The script for claiming a policy reads the policy details from the contract to determine the required coverage amount. It then calls the `claimPolicy` function, sending the coverage value to the contract. In this example, the policy claimed has the ID of `0`. ```solidity title="script/MinTemp.s.sol" contract ClaimPolicy is Script { function run(uint256 policyId) external { uint256 insurerPrivateKey = vm.envUint("PRIVATE_KEY"); MinTempAgency agency = _getAgency(); MinTempAgency.Policy memory policy = agency.getPolicy(policyId); require(policy.status == MinTempAgency.PolicyStatus.Unclaimed, "Policy not in Unclaimed state"); vm.startBroadcast(insurerPrivateKey); agency.claimPolicy{value: policy.coverage}(policyId); vm.stopBroadcast(); } // ... } ``` **Run the script (replace `` with the actual ID, e.g., 0):** ```bash forge script script/MinTemp.s.sol:ClaimPolicy --rpc-url $COSTON2_RPC_URL --broadcast --sig "run(uint256)" ``` The script for resolving a policy is slightly more complicated. It involves making a Web2Json attestation request to the FDC and providing the returned proof to the `MinTempAgency` contract. To learn more about the Web2Json attestation request look at the [related guide](/fdc/guides/foundry/web2-json), or its [spec](/fdc/attestation-types/web2-json). The URL we will be submitting to the FDC is the one already mentioned above. We prepare the URL using a helper function in our script, dividing the coordinates by 106 to return them to their original value. We need to specify the `jq` filter that the FDC should apply to the data received in the response. Only a few fields are needed, and most must first be multiplied by 106 so that we can store them as `uint256` or `int256` values in Solidity. The filter we will be using is: ```jq { latitude: (.coord.lat | if . !== null then .*1000000 else 0 end | floor), longitude: (.coord.lon | if . !== null then .*1000000 else 0 end | floor), description: .weather[0].description, temperature: (.main.temp | if . !== null then .*1000000 else 0 end | floor), minTemp: (.main.temp_min | if . !== null then .*1000000 else 0 end | floor), windSpeed: (.wind.speed | if . !== null then . *1000000 else 0 end | floor), windDeg: .wind.deg } ``` The Foundry scripts workflow automates the three-step FDC process: preparing the request, submitting it, and executing the resolution with the final proof. ##### Step 4.1: Prepare the Resolve Request This script prepares the `Web2Json` attestation request needed to fetch the weather data. It constructs the API query parameters and the `jq` filter, then calls the FDC verifier to get the final `abiEncodedRequest`. ```solidity title="script/MinTemp.s.sol" contract PrepareResolveRequest is Script { function run(uint256 policyId) external { MinTempAgency agency = _getAgency(); MinTempAgency.Policy memory policy = agency.getPolicy(policyId); bytes memory abiEncodedRequest = prepareFdcRequest(policy.latitude, policy.longitude); FdcBase.writeToFile(dirPath, "_resolve_request.txt", StringsBase.toHexString(abiEncodedRequest), true); } // ... helper functions for preparing API and FDC requests ... } ``` **Run the script:** ```bash forge script script/MinTemp.s.sol:PrepareResolveRequest --rpc-url $COSTON2_RPC_URL --broadcast --ffi --sig "run(uint256)" ``` ##### Step 4.2: Submit the Resolve Request This script takes the prepared request from the previous step and submits it to the `FdcHub` contract, saving the `votingRoundId` for the final step. ```solidity title="script/MinTemp.s.sol" contract SubmitResolveRequest is Script { function run() external { string memory requestHex = vm.readFile(string.concat(dirPath, "_resolve_request.txt")); bytes memory abiEncodedRequest = vm.parseBytes(requestHex); uint256 submissionTimestamp = FdcBase.submitAttestationRequest(abiEncodedRequest); uint256 submissionRoundId = FdcBase.calculateRoundId(submissionTimestamp); FdcBase.writeToFile(dirPath, "_resolve_roundId.txt", Strings.toString(submissionRoundId), true); } } ``` **Run the script:** ```bash forge script script/MinTemp.s.sol:SubmitResolveRequest --rpc-url $COSTON2_RPC_URL --broadcast ``` ##### Step 4.3: Execute the Resolution After waiting for the voting round to finalize (90-180 seconds), this script retrieves the proof from the Data Availability Layer and calls the `resolvePolicy` function on the `MinTempAgency` contract, providing the proof. ```solidity title="script/MinTemp.s.sol" contract ExecuteResolve is Script { function run(uint256 policyId) external { // ... read requestHex and roundIdStr from files ... IFdcVerification fdcVerification = ContractRegistry.getFdcVerification(); uint8 protocolId = fdcVerification.fdcProtocolId(); bytes memory proofData = FdcBase.retrieveProof(protocolId, requestHex, submissionRoundId); // Decode proofData into IWeb2Json.Proof FdcBase.ParsableProof memory parsableProof = abi.decode(proofData, (FdcBase.ParsableProof)); IWeb2Json.Response memory response = abi.decode(parsableProof.responseHex, (IWeb2Json.Response)); IWeb2Json.Proof memory finalProof = IWeb2Json.Proof(parsableProof.proofs, response); uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); agency.resolvePolicy(policyId, finalProof); vm.stopBroadcast(); } // ... } ``` **Run the script:** ```bash forge script script/MinTemp.s.sol:ExecuteResolve --rpc-url $COSTON2_RPC_URL --broadcast --ffi --sig "run(uint256)" ``` A full life-cycle of the more elaborate `WeatherIdAgency` (which keys on OpenWeatherMap's `weatherId` code) is implemented in `script/WeatherId.s.sol` in the [Flare Foundry starter](https://github.com/flare-foundation/flare-foundry-starter). The flow mirrors the steps above but uses different policy parameters. ### Modifications and enhancements In the last section of this guide, we will describe several options for improving the described example. We can diversify the offered policy types, which would require only a small adjustment to the existing code. But an even further improvement and a paradigm shift would be to issue tokens to insurers and policyholders. #### Additional data An example of how the policy types can be extended is provided in the [Flare Foundry starter repository](https://github.com/flare-foundation/flare-foundry-starter). The `WeatherIdAgency` checks that the ID of the current weather, as described by the [OpenWeather specification](https://openweathermap.org/weather-conditions), matches or exceeds the weather code threshold within the policy. Other simple modification options are: - maximum temperature - atmospheric pressure - humidity - rainfall - snowfall - visibility - wind speed #### Issuing tokens Instead of running the insurance agency as a registry, we could issue tokens to represent the policies. One option would be to create an NFT when a policy is created, representing the policyholder's position, and a second NFT for the insurer's position when a policy is claimed. This would prevent either party's funds from being locked within a contract for the duration of the policy. Another option would be to issue ERC1155 tokens expressing the stake. It would enable the trade of fractions of policies while optimizing gas consumption. No doubt there is an even better token type for such use case. But that goes beyond the purpose of this guide. --- ## Web2Json for Custom API The [Web2Json guide](/fdc/guides/foundry/web2-json) demonstrates how the Flare Data Connector can be used to fetch Web2 and store it on the chain. The code for this and other examples is available within the [Flare Foundry starter](https://github.com/flare-foundation/flare-foundry-starter) repository. In this guide, we will see how the `Web2Json` example script within the Flare Foundry starter can be modified to work with custom data and custom contracts. That way, the example code can serve as the base building block for a custom project. ## Necessary modifications In order to run on custom data, the example code needs to be modified in four places only. Those are: 1. The contract within the `src/fdcExample/Web2Json.sol` file. 2. The attestation request parameters at the top of the `script/fdcExample/Web2Json.s.sol`. In particular the parameters: - `apiUrl` - `postProcessJq` - `abiSignature` 3. The `DeployContract` script within the `script/fdcExample/Web2Json.s.sol` file. 4. The `InteractWithContract` contract within the `script/fdcExample/Web2Json.s.sol` file. ## Contract The contract within the `src/fdcExample/Web2Json.sol` file should be changed to reflect the project goals. It could be omitted entirely and replaced with multiple files. The only requirement is that at least one contract - the one interacting with FDC - implements a function that accepts an `IWeb2Json.Proof` struct parameter. ## Attestation request parameters ```solidity title="script/fdcExample/Web2Json.s.sol" // Setting request data string public apiUrl = "https://swapi.info/api/people/3"; string public httpMethod = "GET"; string public headers = ""; // defaults to Content-Type: application/json string public queryParams = "{}"; string public body = "{}"; string public postProcessJq = '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split(\\"/\\") | .[-1] | . * 1)}'; string public abiSignature = '{\\"components\\": [{\\"internalType\\": \\"string\\", \\"name\\": \\"name\\", \\"type\\": \\"string\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"height\\", \\"type\\": \\"uint256\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"mass\\", \\"type\\": \\"uint256\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"numberOfFilms\\", \\"type\\": \\"uint256\\"},{\\"internalType\\": \\"uint256\\", \\"name\\": \\"uid\\", \\"type\\": \\"uint256\\"}],\\"name\\": \\"task\\",\\"type\\": \\"tuple\\"}'; ``` The attestation request parameters should describe the new Web2 source. The `apiUrl` is the URL of the API. It can additionally be configured with the `headers`, `queryParams`, and `body` fields. A different `httpMethod` can also be selected. The `postProcessJq` is the jq-filter that will be applied to the JSON data retrieved from the `apiUrl` API. It rearranges and modifies the input JSON into a new JSON. The `postProcessJq` filter can be workshopped using an online tool, like [The JQ Playground](https://play.jqlang.org). The `abiSignature` parameter determines how the modified JSON should be represented in Solidity. It is the ABI signature of an appropriate struct. The easiest way to acquire it is to create a `DataTransportObject` struct with the correct fields within some solidity file. Then, create a public dummy function that accepts a `DataTransportObject` parameter. After running `forge build` (you might need to run `forge clean` first), the contract artifact will be created within the `out/` directory, in the directory that corresponds to the contract with the dummy function. The dummy function can be searched for within the file of its parent contract, and the `abiSignature` read from its `inputs` field. ## Deploy script ```solidity title="script/fdcExample/Web2Json.s.sol" contract DeployContract is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); StarWarsCharacterList characterList = new StarWarsCharacterList(); address _address = address(characterList); vm.stopBroadcast(); Base.writeToFile( dirPath, string.concat(attestationTypeName, "_address"), StringsBase.toHexString(abi.encodePacked(_address)), true ); } } ``` The `DeployContract` script should be modified to deploy and verify the new contract. If the contract is deployed and verified by another script, and thus the script will only interact with an existing contract, the script can be omitted. ## Interact with contract script ```solidity title="script/fdcExample/Web2Json.s.sol" contract InteractWithContract is Script { function run() external { string memory addressString = vm.readLine( string.concat(dirPath, attestationTypeName, "_address", ".txt") ); address _address = vm.parseAddress(addressString); string memory proofString = vm.readLine( string.concat(dirPath, attestationTypeName, "_proof", ".txt") ); bytes memory proofBytes = vm.parseBytes(proofString); IWeb2Json.Proof memory proof = abi.decode( proofBytes, (IWeb2Json.Proof) ); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IStarWarsCharacterList characterList = IStarWarsCharacterList(_address); characterList.addCharacter(proof); vm.stopBroadcast(); } } ``` The `InteractWithContract` script should be modified to interact with the new contract. Unless the dApp requires a more intricate interaction with the new contract, only the last few lines should be fundamentally changed. Likely, only the parameter type and the contract functions called should change. --- ## Cross-Chain Payment We will now demonstrate how the FDC protocol can be used to verify a payment that occurred on one chain (e.g., Sepolia testnet) and trigger an action on another (e.g., Coston2). In this example, verifying a specific USDC transfer on Sepolia will result in the minting of a commemorative NFT on Coston2. In this guide, we will follow the steps outlined under User Workflow in the [FDC overview](/fdc/overview). Our implementation requires handling the FDC voting round finalization process. To manage this, we will use separate scripts in `script/crossChainPayment.s.sol` that handle different stages of the validation process: ```solidity title="script/crossChainPayment.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; // ... other imports contract DeployCrossChainPayment is Script { ... } contract PrepareAttestationRequest is Script { ... } contract SubmitAttestationRequest is Script { ... } contract RetrieveProof is Script { ... } contract MintNFT is Script { ... } ``` The names of the included contracts mirror the steps described in the [FDC guide](/fdc/overview). To bridge the separate executions of the scripts, we will save the relevant data of each script to `.txt` files. :::info The code used in this guide is mostly taken from the [Flare Foundry starter repository](https://github.com/flare-foundation/flare-foundry-starter). ::: ### 1. Deploy Contracts Before starting the FDC workflow, we need to deploy our contracts. This process involves deploying the necessary infrastructure and the application-specific consumer contract. #### Deploy Infrastructure & Consumer The `DeployCrossChainPayment` script deploys the `MyNFT` and `NFTMinter` contracts and correctly configures the `MINTER_ROLE` so that only the `NFTMinter` contract can mint new NFTs. It then saves their addresses to `nftAddress.txt` and `minterAddress.txt`. ```solidity title="script/crossChainPayment.s.sol" contract DeployCrossChainPayment is Script { function run() external returns (address nftAddr, address minterAddr) { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address deployerAddress = vm.addr(deployerPrivateKey); vm.startBroadcast(deployerPrivateKey); MyNFT nft = new MyNFT(deployerAddress, deployerAddress); NFTMinter minter = new NFTMinter(nft); bytes32 minterRole = nft.MINTER_ROLE(); nft.grantRole(minterRole, address(minter)); nft.revokeRole(minterRole, deployerAddress); vm.stopBroadcast(); nftAddr = address(nft); minterAddr = address(minter); vm.createDir(dirPath, true); string memory nftPath = string.concat(dirPath, "_nftAddress.txt"); string memory minterPath = string.concat(dirPath, "_minterAddress.txt"); vm.writeFile(nftPath, vm.toString(nftAddr)); vm.writeFile(minterPath, vm.toString(minterAddr)); console.log("MyNFT deployed to:", nftAddr); console.log("NFTMinter deployed to:", minterAddr); } } ``` Run the deployment script with the following command: ```bash forge script script/crossChainPayment.s.sol:DeployCrossChainPayment --rpc-url coston2 --broadcast ``` ### 2. Prepare Request The JSON request to the verifier contains the `attestationType`, `sourceId`, and a `requestBody`. #### Required Fields For the `EVMTransaction` type, the `requestBody` is a JSON object containing these fields: - `transactionHash`: The hash of the transaction to verify, as a string. - `requiredConfirmations`: The number of blocks to wait for, as a string. `1` is sufficient for this example. - `provideInput`: `true` to include transaction input data in the response. - `listEvents`: `true` to include decoded logs/events in the response. - `logIndices`: An array of specific log indices to include. An empty array `[]` includes all. #### Reference Documentation - [EVMTransaction Specification](/fdc/attestation-types/evm-transaction) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/eth/api-doc#/) #### Example Script The `PrepareAttestationRequest` script constructs and posts this request to the verifier. ```solidity title="script/crossChainPayment.s.sol" contract PrepareAttestationRequest is Script { using Surl for *; string public constant TRANSACTION_HASH = "0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c"; string public constant SOURCE_NAME = "testETH"; // Chain ID for Sepolia string public constant BASE_SOURCE_NAME = "eth"; function prepareRequestBody() private pure returns (string memory) { return string.concat('{"transactionHash":"', TRANSACTION_HASH, '","requiredConfirmations":"1","provideInput":true,"listEvents":true,"logIndices":[]}'); } function run() external { vm.createDir(dirPath, true); string memory attestationType = FdcBase.toUtf8HexString(attestationTypeName); string memory sourceId = FdcBase.toUtf8HexString(SOURCE_NAME); string memory requestBody = prepareRequestBody(); (string[] memory headers, string memory body) = FdcBase.prepareAttestationRequest(attestationType, sourceId, requestBody); string memory baseUrl = vm.envString("VERIFIER_URL_TESTNET"); string memory url = string.concat(baseUrl, "/verifier/", BASE_SOURCE_NAME, "/", attestationTypeName, "/prepareRequest"); (, bytes memory data) = url.post(headers, body); FdcBase.AttestationResponse memory response = FdcBase.parseAttestationRequest(data); FdcBase.writeToFile(dirPath, string.concat(attestationTypeName, "_abiEncodedRequest.txt"), StringsBase.toHexString(response.abiEncodedRequest), true); console.log("Successfully prepared attestation request and saved to file."); } } ``` Run the script to prepare the request: ```bash forge script script/crossChainPayment.s.sol:PrepareAttestationRequest --rpc-url coston2 --broadcast --ffi ``` ### 3. Submit Request to FDC This step takes the ABI-encoded request generated by the verifier and submits it to the FDC Hub on the Flare network. The `SubmitAttestationRequest` script reads the request from the file, submits it in a transaction, and saves the resulting `votingRoundId` for the next step. ```solidity title="script/crossChainPayment.s.sol" contract SubmitAttestationRequest is Script { function run() external { string memory requestStr = vm.readFile(string.concat(dirPath, attestationTypeName, "_abiEncodedRequest.txt")); bytes memory request = vm.parseBytes(requestStr); uint256 timestamp = FdcBase.submitAttestationRequest(request); uint256 votingRoundId = FdcBase.calculateRoundId(timestamp); FdcBase.writeToFile(dirPath, string.concat(attestationTypeName, "_votingRoundId.txt"), Strings.toString(votingRoundId), true); console.log("Successfully submitted request. Voting Round ID:", votingRoundId); } } ``` Run the script to submit the request: ```bash forge script script/crossChainPayment.s.sol:SubmitAttestationRequest --rpc-url coston2 --broadcast ``` ### 4. Retrieve Proof After waiting for the voting round to be finalized (max. 180 seconds), we can retrieve the proof from a Data Availability Layer provider. You can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to the [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. The `RetrieveProof` script waits for finalization, polls the DA layer, and saves the complete proof to a file. ```solidity title="script/crossChainPayment.s.sol" contract RetrieveProof is Script { function run() external { string memory requestHex = vm.readFile(string.concat(dirPath, attestationTypeName, "_abiEncodedRequest.txt")); uint256 votingRoundId = FdcBase.stringToUint(vm.readFile(string.concat(dirPath, attestationTypeName, "_votingRoundId.txt"))); IFdcVerification fdcVerification = ContractRegistry.getFdcVerification(); uint8 protocolId = fdcVerification.fdcProtocolId(); bytes memory proofData = FdcBase.retrieveProof(protocolId, requestHex, votingRoundId); FdcBase.ParsableProof memory proof = abi.decode(proofData, (FdcBase.ParsableProof)); IEVMTransaction.Response memory proofResponse = abi.decode(proof.responseHex, (IEVMTransaction.Response)); IEVMTransaction.Proof memory finalProof = IEVMTransaction.Proof(proof.proofs, proofResponse); FdcBase.writeToFile(dirPath, string.concat(attestationTypeName, "_proof.txt"), StringsBase.toHexString(abi.encode(finalProof)), true); console.log("Successfully retrieved proof and saved to file."); } } ``` Run the script to retrieve the proof: ```bash forge script script/crossChainPayment.s.sol:RetrieveProof --rpc-url coston2 --broadcast --ffi ``` ### 5. Use the Data The final step is to use the verified data on-chain. The `MintNFT` script reads the proof and the `minterAddress` from their respective files. It then calls the `collectAndProcessTransferEvents` function on the `NFTMinter` contract. The `NFTMinter` contract first verifies the proof by calling `IFdcVerification.verifyEVMTransaction()`. If the proof is valid, it decodes the event data from the proof to confirm that the USDC transfer meets the required conditions (e.g., correct recipient, minimum amount). If all conditions are met, it mints a new NFT to the original sender. #### Example Script ```solidity title="script/crossChainPayment.s.sol" contract MintNFT is Script { function run() external { string memory configPath = string.concat(dirPath, "_minterAddress.txt"); address minterAddress = vm.parseAddress(vm.readFile(configPath)); string memory proofString = vm.readFile(string.concat(dirPath, attestationTypeName, "_proof.txt")); bytes memory proofBytes = vm.parseBytes(proofString); IEVMTransaction.Proof memory proof = abi.decode(proofBytes, (IEVMTransaction.Proof)); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); NFTMinter minter = NFTMinter(payable(minterAddress)); minter.collectAndProcessTransferEvents(proof); vm.stopBroadcast(); console.log("Successfully sent proof to NFTMinter contract."); TokenTransfer[] memory transfers = minter.getTokenTransfers(); require(transfers.length > 0, "No token transfer was recorded."); console.log("--- Verification ---"); console.log("Recorded Transfer From:", transfers[0].from); console.log("Recorded Transfer To:", transfers[0].to); console.log("Recorded Transfer Value:", transfers[0].value); } } ``` Run the script to send the proof to the `NFTMinter` contract and mint the NFT: ```bash forge script script/crossChainPayment.s.sol:MintNFT --rpc-url coston2 --broadcast ``` --- ## Cross-Chain FDC This guide demonstrates a powerful cross-chain workflow using the Flare Data Connector (FDC). We will fetch data from a public Web2 API using the [Web2Json](/fdc/attestation-types/web2-json) attestation type on a Flare network (Coston2) and then use the resulting proof to trigger a state change on a different EVM-compatible blockchain, the XRPL EVM Sidechain Testnet. The key to this cross-chain interaction is the `Relay` contract. For a non-Flare chain to verify FDC proofs, the Merkle root of each finalized voting round on Flare must be transmitted to the target chain. This is handled by the `Relay` contract, which must be deployed on the target chain. For the XRPL EVM Sidechain, Flare provides a relayer service where a backend script listens for `Relay` transactions on Flare and copies them to the `Relay` contract on the target chain. This allows a verifier contract on the target chain to confirm the validity of FDC proofs without trusting a centralized intermediary. Before running any code, check if the `Relay` contract is already being relayed, or submit a request to the Flare team. For this example, we will: 1. Deploy a custom verification infrastructure to the XRPL EVM Sidechain Testnet. 2. Request data about a Star Wars character from the `swapi.info` API on the Coston2 Testnet. 3. Submit this request to the FDC on Coston2. 4. Retrieve the finalized proof and use it to call a consumer contract on the XRPL EVM Sidechain, which verifies the proof and stores the character's data on-chain. :::info The code used in this guide is available in the [Flare Foundry starter repository](https://github.com/flare-foundation/flare-foundry-starter). ::: ### 1. Deploy Infrastructure on Target Chain (XRPL EVM Sidechain) This workflow requires a one-time deployment of a persistent infrastructure on the target chain. These core contracts manage contract addresses and proof verification. - **`AddressUpdater`**: A governance-controlled contract that maintains a registry of key contract addresses, such as the `Relay`. - **`FdcVerification`**: A custom verification contract that retrieves the trusted Merkle root from the `Relay` to verify FDC proofs on the target chain. The protocol ID (e.g., 200 for Coston2) configured in this contract must match the FDC instance on the source Flare network. The `DeployInfrastructure` script handles this setup. On a non-Flare chain, the `Relay` address is a known, pre-deployed address that must be provided in your `.env` file. ```solidity title="script/CrossChainFdc.s.sol" // Deploys the persistent CORE INFRASTRUCTURE contracts to the target non-Flare chain. contract DeployInfrastructure is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address governance = vm.addr(deployerPrivateKey); // On a non-Flare chain, the Relay address is a known, pre-deployed address. // It must be provided in the .env file. address relayAddress = vm.envAddress("RELAY_ADDRESS"); console.log("Using Relay address from .env file:", relayAddress); require(relayAddress != address(0), "Error: RELAY_ADDRESS not set in .env or invalid."); vm.startBroadcast(deployerPrivateKey); AddressUpdater addressUpdater = new AddressUpdater(governance); // The protocol ID must match the one used by the FDC instance on the Flare network (e.g., 200 for Coston2). FdcVerification fdcVerification = new FdcVerification(address(addressUpdater), 200); string[] memory names = new string[](2); address[] memory addresses = new address[](2); names[0] = "Relay"; addresses[0] = relayAddress; names[1] = "AddressUpdater"; addresses[1] = address(addressUpdater); addressUpdater.addOrUpdateContractNamesAndAddresses(names, addresses); IIAddressUpdatable[] memory contractsToUpdate = new IIAddressUpdatable[](1); contractsToUpdate[0] = fdcVerification; addressUpdater.updateContractAddresses(contractsToUpdate); vm.stopBroadcast(); vm.createDir(dirPath, true); vm.writeFile(string.concat(dirPath, "addressUpdater.txt"), vm.toString(address(addressUpdater))); vm.writeFile(string.concat(dirPath, "fdcVerification.txt"), vm.toString(address(fdcVerification))); console.log("\n--- Infrastructure Deployment Complete on Chain ID:", block.chainid, "---"); console.log("AddressUpdater: ", address(addressUpdater)); console.log("FdcVerification: ", address(fdcVerification)); } } ``` Run the script to deploy the core contracts to the XRPL EVM Sidechain. This only needs to be done once. ```bash forge script script/CrossChainFdc.s.sol:DeployInfrastructure --rpc-url $XRPLEVM_RPC_URL_TESTNET --broadcast ``` ### 2. Prepare and Submit Request on Source Chain (Coston2) This step, executed by the `PrepareAndSubmitRequest` script, must be run on a Flare network (Coston2). It prepares the `Web2Json` request off-chain by calling the verifier API and then immediately submits the resulting `abiEncodedRequest` to the FDC on-chain. The script saves the `abiEncodedRequest` and the resulting `votingRoundId` to files, which will be needed for the final step on the target chain. ```solidity title="script/CrossChainFdc.s.sol" // Prepares and submits the FDC request on the Flare Network. contract PrepareAndSubmitRequest is Script { using Surl for *; string public constant SOURCE_NAME = "PublicWeb2"; function run() external { console.log("--- Preparing and Submitting FDC request on Chain ID:", block.chainid, "---"); vm.createDir(dirPath, true); string memory attestationType = FdcBase.toUtf8HexString(attestationTypeName); string memory sourceId = FdcBase.toUtf8HexString(SOURCE_NAME); string memory apiUrl = "https://swapi.info/api/people/3"; // C-3PO string memory postProcessJq = '{name: .name, height: .height, mass: .mass, numberOfMovies: .films | length, apiUid: (.url | split(\\"/\\") | .[-1] | tonumber)}'; string memory abiSignature = '{\\"components\\":[{\\"internalType\\":\\"string\\",\\"name\\":\\"name\\",\\"type\\":\\"string\\"},{\\"internalType\\":\\"uint256\\",\\"name\\":\\"height\\",\\"type\\":\\"uint256\\"},{\\"internalType\\":\\"uint256\\",\\"name\\":\\"mass\\",\\"type\\":\\"uint256\\"},{\\"internalType\\":\\"uint256\\",\\"name\\":\\"numberOfMovies\\",\\"type\\":\\"uint256\\"},{\\"internalType\\":\\"uint256\\",\\"name\\":\\"apiUid\\",\\"type\\":\\"uint256\\"}],\\"name\\":\\"dto\\",\\"type\\":\\"tuple\\"}'; string memory requestBody = string.concat('{"url":"',apiUrl,'","httpMethod":"GET","headers":"{}","queryParams":"{}","body":"{}","postProcessJq":"',postProcessJq,'","abiSignature":"',abiSignature,'"}'); // Prepare request off-chain (string[] memory headers, string memory body) = FdcBase.prepareAttestationRequest(attestationType, sourceId, requestBody); string memory baseUrl = vm.envString("VERIFIER_URL_TESTNET"); string memory url = string.concat(baseUrl, "/verifier/web2/", attestationTypeName, "/prepareRequest"); (, bytes memory data) = url.post(headers, body); FdcBase.AttestationResponse memory response = FdcBase.parseAttestationRequest(data); // Submit request on-chain uint256 timestamp = FdcBase.submitAttestationRequest(response.abiEncodedRequest); uint256 votingRoundId = FdcBase.calculateRoundId(timestamp); // Write data to files for the next step FdcBase.writeToFile(dirPath, "abiEncodedRequest.txt", StringsBase.toHexString(response.abiEncodedRequest), true); FdcBase.writeToFile(dirPath, "votingRoundId.txt", Strings.toString(votingRoundId), true); console.log("Successfully prepared and submitted request. Voting Round ID:", votingRoundId); } } ``` Run the script on **Coston2**: ```bash forge script script/CrossChainFdc.s.sol:PrepareAndSubmitRequest --rpc-url $COSTON2_RPC_URL --broadcast --ffi ``` ### 3. Deliver Proof to Consumer on Target Chain (XRPL EVM Sidechain) The final script, `DeliverProofToContract`, is executed on the target chain (XRPL EVM Sidechain). It handles deploying the consumer contract, retrieving the proof, and delivering it for verification. 1. **Deploy Consumer Contract**: It first deploys the `StarWarsCharacterListV3` contract, passing it the address of the `FdcVerification` contract deployed in Step 1. 2. **Retrieve Proof**: It reads the `abiEncodedRequest` and `votingRoundId` from the files created in Step 2. After waiting for the voting round on Flare to finalize (max. 180 seconds), it polls the Data Availability layer to get the proof. 3. **Interact with Consumer**: It calls `addCharacter` on the deployed consumer contract, passing the `finalProof`. A `value` of 1 wei is sent to cover the fee required by the `Relay` contract to fetch the Merkle root from the source chain. ```solidity title="script/CrossChainFdc.s.sol" // Deploys the consumer, waits, retrieves proof, and delivers it on the target chain. contract DeliverProofToContract is Script { function run() external { console.log("--- Delivering Proof on Chain ID:", block.chainid, "---"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // --- Deploy Consumer Contract --- string memory fdcVerificationPath = string.concat(dirPath, "fdcVerification.txt"); require(vm.exists(fdcVerificationPath), "Infrastructure not deployed. Run DeployInfrastructure first."); address fdcVerificationAddress = vm.parseAddress(vm.readFile(fdcVerificationPath)); vm.startBroadcast(deployerPrivateKey); StarWarsCharacterListV3 characterList = new StarWarsCharacterListV3(fdcVerificationAddress); vm.stopBroadcast(); console.log("StarWarsCharacterListV3 consumer deployed to:", address(characterList)); // --- Retrieve Proof and Interact --- string memory requestHex = vm.readFile(string.concat(dirPath, "abiEncodedRequest.txt")); uint256 votingRoundId = FdcBase.stringToUint(vm.readFile(string.concat(dirPath, "votingRoundId.txt"))); FdcVerification fdcVerification = FdcVerification(fdcVerificationAddress); uint8 protocolId = fdcVerification.fdcProtocolId(); bytes memory proofData = FdcBase.retrieveProof(protocolId, requestHex, votingRoundId); FdcBase.ParsableProof memory parsedProof = abi.decode(proofData, (FdcBase.ParsableProof)); IWeb2Json.Response memory proofResponse = abi.decode(parsedProof.responseHex, (IWeb2Json.Response)); IWeb2Json.Proof memory finalProof = IWeb2Json.Proof(parsedProof.proofs, proofResponse); console.log("\nDelivering proof to consumer contract..."); vm.startBroadcast(deployerPrivateKey); characterList.addCharacter{value: 1}(finalProof); vm.stopBroadcast(); console.log("Proof successfully delivered!"); StarWarsCharacter[] memory characters = characterList.getAllCharacters(); require(characters.length > 0, "Verification failed: No character was added."); console.log("\n--- Character Added Verification ---"); console.log("Name:", characters[0].name); console.log("BMI:", characters[0].bmi); } } ``` Run the final script on the **XRPL EVM Sidechain**: ```bash forge script script/CrossChainFdc.s.sol:DeliverProofToContract --rpc-url $XRPLEVM_RPC_URL_TESTNET --broadcast --ffi ``` --- ## Confirm Block Height This guide focuses on the [ConfirmedBlockHeightExists](/fdc/attestation-types/confirmed-block-height-exists) attestation type, an efficient way to assert whether a specific `blockNumber` is confirmed with additional data to compute the block production rate within a given time window. The primary contract interface for this attestation type is [`IConfirmedBlockHeightExists`](/fdc/reference/IFdcHub). ### Type Definition ```solidity title="ConfirmedBlockHeightExists.sol" // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name ConfirmedBlockHeightExists * @custom:id 0x03 * @custom:supported BTC, DOGE, XRP, testBTC, testDOGE, testXRP * @author Flare * @notice An assertion that a block with `blockNumber` is confirmed. * It also provides data to compute the block production rate in the given time range. * @custom:verification It is checked that the block with `blockNumber` is confirmed by at least `numberOfConfirmations`. * If it is not, the request is rejected. We note a block on the tip of the chain is confirmed by 1 block. * Then `lowestQueryWindowBlock` is determined and its number and timestamp are extracted. * * * Current confirmation heights consensus: * * * | `Chain` | `chainId` | `numberOfConfirmations` | `timestamp ` | * | ------- | --------- | ----------------------- | ------------ | * | `BTC` | 0 | 6 | mediantime | * | `DOGE` | 2 | 60 | mediantime | * | `XRP` | 3 | 3 | close_time | * * * * * @custom:lut `lowestQueryWindowBlockTimestamp` */ interface ConfirmedBlockHeightExists { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response as defined. * @param requestBody Data defining the request. Type (struct) and interpretation is determined by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the Data Connector round in which the request was considered. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction of the response body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for ConfirmedBlockHeightExistsType attestation type * @param blockNumber The number of the block the request wants a confirmation of. * @param queryWindow The length of the period in which the block production rate is to be computed. */ struct RequestBody { uint64 blockNumber; uint64 queryWindow; } /** * @notice Response body for ConfirmedBlockHeightExistsType attestation type * @custom:below `blockNumber`, `lowestQueryWindowBlockNumber`, `blockTimestamp` and `lowestQueryWindowBlockTimestamp` can be used to compute the average block production time in the specified block range. * @param blockTimestamp The timestamp of the block with `blockNumber`. * @param numberOfConfirmations The depth at which a block is considered confirmed depending on the chain. All attestation providers must agree on this number. * @param lowestQueryWindowBlockNumber The block number of the latest block that has a timestamp strictly smaller than `blockTimestamp` - `queryWindow`. * @param lowestQueryWindowBlockTimestamp The timestamp of the block at height `lowestQueryWindowBlockNumber`. */ struct ResponseBody { uint64 blockTimestamp; uint64 numberOfConfirmations; uint64 lowestQueryWindowBlockNumber; uint64 lowestQueryWindowBlockTimestamp; } } ``` The request body is quite simple. You provide the `blockNumber` you want to confirm exists on the chain and the `queryWindow`—the length of the period in which the block production rate is to be computed (relative to the timestamp of the block you are requesting). Importantly, for the block to be considered visible, at least `X` blocks above it must be confirmed. This ensures that blocks not on the main chain are not confirmed. The number of confirmations required varies by chain and is listed in the specification. #### What Do You Get in Return? As per the specification, you only receive information confirming that the block with `blockNumber` is confirmed by at least `numberOfConfirmations`. If the block is not confirmed, the request is rejected (none of the attestation clients will confirm the response, and it will not be included in the Merkle tree). The response body contains the following fields: - **`blockTimestamp`**: The timestamp of the block with `blockNumber`. - **`numberOfConfirmations`**: The depth at which a block is considered confirmed depending on the chain. This is fixed per chain and specified in the documentation. - **`lowestQueryWindowBlockNumber`**: The block number of the latest block that has a timestamp strictly smaller than `blockTimestamp` - `queryWindow`. This allows you to gauge the average block production time in the specified block range. - **`lowestQueryWindowBlockTimestamp`**: The timestamp of the block at height `lowestQueryWindowBlockNumber`, indicating when the block was produced. ### Example To check the top block, you would typically query the RPC of the chain, get the top block, subtract the number of confirmations, and then query the attestation client to get the result. Alternatively, you can piggyback on the previous example, create a transaction, see the block it was included in, and proceed from there. Each attestation provider also exposes several diagnostic endpoints that allow you to get information about the chain it is operating on. The endpoint that is particularly interesting for this purpose is the `block-range` endpoint, which returns the range of blocks the attestation provider is currently observing. By querying the `block-range` endpoint, you can get the range of blocks the attestation provider is observing and then request the confirmation of the top block in that range. This approach allows you to verify the top block efficiently without manually tracking the block production and confirmation process. Use the following code (also found in `tryConfirmedBlockHeightExists.ts`) and try to see how `prepareResponse` fares for blocks that are out of range for the current confirmation limit. This will help you understand how the attestation client handles requests for blocks that have not yet reached the necessary number of confirmations. ```typescript title="tryConfirmedBlockHeightExists.ts" const { ATTESTATION_URL, ATTESTATION_API_KEY } = process.env; function toHex(data: string): string { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return "0x" + result.padEnd(64, "0"); } function fromHex(data: string): string { data = data.replace(/^(0x\.)/, ""); return data .split(/(\w\w)/g) .filter((p) => !!p) .map((c) => String.fromCharCode(parseInt(c, 16))) .join(""); } async function prepareAttestationResponse( attestationType: string, network: string, sourceId: string, requestBody: any, ): Promise { const response = await fetch( `${ATTESTATION_URL}/verifier/${network}/${attestationType}/prepareResponse`, { method: "POST", headers: { "X-API-KEY": ATTESTATION_API_KEY as string, "Content-Type": "application/json", }, body: JSON.stringify({ attestationType: toHex(attestationType), sourceId: toHex(sourceId), requestBody: requestBody, }), }, ); const data = await response.json(); return data; } async function getVerifierBlockRange(network: string): Promise { return ( await ( await fetch( `${ATTESTATION_URL}/verifier/${network}/api/indexer/block-range`, { method: "GET", headers: { "X-API-KEY": ATTESTATION_API_KEY as string, "Content-Type": "application/json", }, }, ) ).json() ).data; } async function main() { const btcRange = await getVerifierBlockRange("btc"); const dogeRange = await getVerifierBlockRange("doge"); const xrplRange = await getVerifierBlockRange("xrp"); console.log("BTC Range: ", btcRange); console.log( await prepareAttestationResponse( "ConfirmedBlockHeightExists", "btc", "testBTC", { blockNumber: btcRange.last.toString(), queryWindow: "123", }, ), ); console.log("DOGE Range: ", dogeRange); console.log( await prepareAttestationResponse( "ConfirmedBlockHeightExists", "doge", "testDOGE", { blockNumber: dogeRange.last.toString(), queryWindow: "123", }, ), ); console.log("XRPL Range: ", xrplRange); console.log( await prepareAttestationResponse( "ConfirmedBlockHeightExists", "xrp", "testXRP", { blockNumber: xrplRange.last.toString(), queryWindow: "123", }, ), ); } main().then(() => process.exit(0)); ``` Which will output a response similar to this: ```json BTC Range: { first: 2578997, last: 2579392 } { status: 'VALID', response: { attestationType: '0x436f6e6669726d6564426c6f636b486569676874457869737473000000000000', sourceId: '0x7465737442544300000000000000000000000000000000000000000000000000', votingRound: '0', lowestUsedTimestamp: '1708812188', requestBody: { blockNumber: '2579392', queryWindow: '123' }, responseBody: { blockTimestamp: '1708812188', numberOfConfirmations: '6', lowestQueryWindowBlockNumber: '2579391', lowestQueryWindowBlockTimestamp: '1708812020' } } } DOGE Range: { first: 5706001, last: 5974548 } { status: 'VALID', response: { attestationType: '0x436f6e6669726d6564426c6f636b486569676874457869737473000000000000', sourceId: '0x74657374444f4745000000000000000000000000000000000000000000000000', votingRound: '0', lowestUsedTimestamp: '1708819752', requestBody: { blockNumber: '5974548', queryWindow: '123' }, responseBody: { blockTimestamp: '1708819752', numberOfConfirmations: '60', lowestQueryWindowBlockNumber: '5974543', lowestQueryWindowBlockTimestamp: '1708819511' } } } XRPL Range: { first: 45585486, last: 45678173 } { status: 'VALID', response: { attestationType: '0x436f6e6669726d6564426c6f636b486569676874457869737473000000000000', sourceId: '0x7465737458525000000000000000000000000000000000000000000000000000', votingRound: '0', lowestUsedTimestamp: '1708822152', requestBody: { blockNumber: '45678173', queryWindow: '123' }, responseBody: { blockTimestamp: '1708822152', numberOfConfirmations: '1', lowestQueryWindowBlockNumber: '45678132', lowestQueryWindowBlockTimestamp: '1708822022' } } } ``` This attestation type is also useful for observing another important response: `INDETERMINATE`. An `INDETERMINATE` response means that the attestation can't be confirmed (yet) because there are not enough confirmations for the block. This response indicates that the attestation client cannot confirm or reject the block for sure, but it might be valid in the future once more confirmations are received. To see this in action, take the provided code and check for a block that has not yet been confirmed by the required amount. The easiest way to do this is to add 10 to the block range and observe the response. If done correctly, the response should be: ```json { "status": "INDETERMINATE" } ``` One important thing to note is that all numbers are sent as strings (either decimal or hex). The main reason for this is that JavaScript does not have a native 64-bit integer type, and numbers are represented as 64-bit floating-point numbers, which can lead to incorrect representation of large numbers. Even though block numbers might not be that large, encoding JSON numbers as strings ensures they are represented correctly. --- ## Connect to EVM Chains This guide is for developers who want to connect to EVM chains. In this guide, you will learn how to: - Use the `EVMTransaction` attestation type. - Understand the implications of connecting account-based chains. - Relay transaction information from Ethereum Sepolia to Flare Testnet Coston2. ## Examples Now that you know how to request an attestation and what you are getting in return, let's explore some examples. These examples are a bit more involved and each will come in a few parts: - Script making a dummy transaction on the `Sepolia` testnet. - Smart contract(s) accepting an attestation request and performing some desired action. - Deployment and run script that ties them together. This deployment script will also allow you to understand exactly how long the waiting for each phase takes, which is something not previously focused on. ### Simple ETH transfer Let's start small. You will create a smart contract that just tallies the top-level amounts transferred to a designated address on Sepolia. The scenario is pretty simple: - You have a "payment" to an Externally Owned Account (EOA - so not a smart contract) on Sepolia, and anyone can send funds there and prove this. - On the Flare side, you will deploy a contract that will accept proofs with data in the proper accounting format: who has sent how much to this end owner address. The full code for this example is in the `scripts/evm/trySimpleTransaction.ts`, `contracts/EthereumPaymentCollector.sol`, and `contracts/FallbackContract` files. You won't be copy-pasting the full code here, but you will go through the most important parts. The setup is now in two parts, and `main` correctly picks up the right part to run depending on the network it is run on. First, deploy a simple `FallbackContract` on Sepolia. ```bash yarn hardhat run scripts/evm/trySimpleTransaction --network sepolia ``` - This contract will just emit an event when the `fallback` function is called. - You will be attesting to this event in the next part. - The script makes two transactions on Sepolia: one with value to an address and one to the address of the contract. - The second transaction will call the `fallback` function and emit the event. - The transaction hashes are logged, and the JSON response of the attestation results is printed (so you can see what you will get in the next part). Here is an example result: ```bash 0xac640ab047aa1097ddd473e5940921eb500a9912b33072b8532617692428830e { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708907688", "requestBody": { "transactionHash": "0xac640ab047aa1097ddd473e5940921eb500a9912b33072b8532617692428830e", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5363670", "timestamp": "1708907688", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429", "value": "10", "input": "0x0123456789", "status": "1", "events": [] } } } 0x7eb54cde238fc700be31c98af7e4df8c4fc96fd5c634c490183ca612a481efcc { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708907712", "requestBody": { "transactionHash": "0x7eb54cde238fc700be31c98af7e4df8c4fc96fd5c634c490183ca612a481efcc", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5363672", "timestamp": "1708907712", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "value": "10", "input": "0x9876543210", "status": "1", "events": [ { "logIndex": "160", "emitterAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "topics": [ "0xaca09dd456ca888dccf8cc966e382e6e3042bb7e4d2d7815015f844edeafce42" ], "data": "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000059876543210000000000000000000000000000000000000000000000000000000", "removed": false } ] } } } ``` After you have the transaction hashes, copy them to the part of the `main` method that will execute the Data Connector part, this time on Coston. Let's take a look at `executeStateConnectorProof`. Here, the Data Connector part comes into play. You have already seen it in the previous guides, so you will just quickly scan through it. The code is a bit more involved, as you are now working with multiple transactions (this is not EVMTransaction specific, but it is a good example of how you can use the Data Connector to do more complex things). Again, you get an encoded attestation request (one for each transaction) and then you submit them to the Data Connector. Once this is done, you wait for the round to be confirmed (see the while loop that takes most of the time) and then you get the proof. The `EthereumPaymentCollector` contract is deployed on Coston with one important method `collectPayment`. This method accepts the `EVMTransaction.Proof` response and does the important accounting. As usual, first check that the provided proof is correct: that the Merkle proof really attests that this transaction was included in the Merkle tree. Then comes the fun part - you can use the information from a transaction to do whatever you want. You won't just write it to the list of all transactions and be done. Instead, you will try to decode the event data and see what you can get from it. As mentioned before, the event data is specific to the event and you need to know the event structure to decode it properly. In this case, you know how it looks, and the decoding is done by the built-in `abi.decode`. You then just push the decoded data in struct form to the list of events and you are done. :::warning `abi.decode` is not type-safe and you can easily get wrong results if you don't know the event structure. Even more, this might be a security risk if you are not careful (or revert unexpectedly), but it is a nice representation of how powerful the events - and their information - can be. ::: Finally, when you have both proofs and the contract deployed, you just call the `collectPayment` method with the proofs, and you are done (unless something goes wrong, then you will have to wait for the next round and try again). The result looks something like: ```bash Rounds: [ '809307', '809307' ] Waiting for the round to be confirmed 809303n 809307 Waiting for the round to be confirmed 809303n 809307 Waiting for the round to be confirmed 809303n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Round confirmed, getting proof Successfully submitted source code for contract contracts/EthereumPaymentCollector.sol:EthereumPaymentCollector at 0x7cf6E7aeFD0207a5bE9a7DbcDA560fc7a6dBD7B4 for verification on the block explorer. Waiting for verification result... Successfully verified contract EthereumPaymentCollector on the block explorer. https://coston-explorer.flare.network/address/0x7cf6E7aeFD0207a5bE9a7DbcDA560fc7a6dBD7B4#code { "data": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "lowestUsedTimestamp": "1708907688", "requestBody": { "listEvents": true, "logIndices": [], "provideInput": true, "requiredConfirmations": "1", "transactionHash": "0xac640ab047aa1097ddd473e5940921eb500a9912b33072b8532617692428830e" }, "responseBody": { "blockNumber": "5363670", "events": [], "input": "0x0123456789", "isDeployment": false, "receivingAddress": "0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "status": "1", "timestamp": "1708907688", "value": "10" }, "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "809307" }, "merkleProof": [ "0x56faf895bbcb0b2a6f3bc283ea5e1793b224dca8b4b99240a34cee6d9bf1b8f3", "0x13ef0de709e7b0485f7623f5a0ad5b56aa23626fbffe5e7f4502bb7be5e0bf7e", "0xf72c31824174676516a9c5d9713cb1ae8866cac71462fe2b1a3c1e1b9418a94f" ] } { "data": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "lowestUsedTimestamp": "1708907712", "requestBody": { "listEvents": true, "logIndices": [], "provideInput": true, "requiredConfirmations": "1", "transactionHash": "0x7eb54cde238fc700be31c98af7e4df8c4fc96fd5c634c490183ca612a481efcc" }, "responseBody": { "blockNumber": "5363672", "events": [ { "data": "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000059876543210000000000000000000000000000000000000000000000000000000", "emitterAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "logIndex": "160", "removed": false, "topics": [ "0xaca09dd456ca888dccf8cc966e382e6e3042bb7e4d2d7815015f844edeafce42" ] } ], "input": "0x9876543210", "isDeployment": false, "receivingAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "status": "1", "timestamp": "1708907712", "value": "10" }, "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "809307" }, "merkleProof": [ "0x8e45d2d564bf7d652cf904a72e53f5e7e34d7e5e184906afda92f755e99cd421", "0x13ef0de709e7b0485f7623f5a0ad5b56aa23626fbffe5e7f4502bb7be5e0bf7e", "0xf72c31824174676516a9c5d9713cb1ae8866cac71462fe2b1a3c1e1b9418a94f" ] } ``` :::info On the previous attestation types, we were only able to get transactions in the last two days (this is attestation type specific). ::: ### Decoding emitted events As previously stated, an event will be the core feature for observing what is happening on other chains. Let's now use this to prove that an ERC20 payment was made on Sepolia and then decode the event to see who made the payment and how much. As before, you will deploy an ERC20 contract on Sepolia, mint some tokens, and send them to an address. The full code is available in the `scripts/evm/tryERC20transfers.ts` and `contracts/MintableERC20.sol` files. A sample response for the ERC20 transaction would look like: ```bash Sepolia USDT deployed to: 0x6023e19d70C304eA16a3728ceDcb042791737EC3 0xd7eed8cf377a4079718e8d709b3648d62a3a16ea39fbfbe759600c3d574caa15 { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708999068", "requestBody": { "transactionHash": "0xd7eed8cf377a4079718e8d709b3648d62a3a16ea39fbfbe759600c3d574caa15", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5370899", "timestamp": "1708999068", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "value": "0", "input": "0x40c10f190000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f37300000000000000000000000000000000000000000000000000000000000f4240", "status": "1", "events": [ { "logIndex": "38", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373" ], "data": "0x00000000000000000000000000000000000000000000000000000000000f4240", "removed": false } ] } } } 0x9dffa80b6daea45ed4bfc93bb72cdb893549fdefb81cb760b7ce08edef9859a6 { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708999080", "requestBody": { "transactionHash": "0x9dffa80b6daea45ed4bfc93bb72cdb893549fdefb81cb760b7ce08edef9859a6", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5370900", "timestamp": "1708999080", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "value": "0", "input": "0xa9059cbb000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d42900000000000000000000000000000000000000000000000000000000000003e8", "status": "1", "events": [ { "logIndex": "32", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ], "data": "0x00000000000000000000000000000000000000000000000000000000000003e8", "removed": false } ] } } } ``` Let's now decode the data you got back and explore the event in a little more detail. ```json { "logIndex": "38", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373" ], "data": "0x00000000000000000000000000000000000000000000000000000000000f4240", "removed": false } { "logIndex": "32", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ], "data": "0x00000000000000000000000000000000000000000000000000000000000003e8", "removed": false } ``` When processing the events, it is important to know which contract should be emitting the event (you don't want to count a memecoin transfer as a USDT transfer). The `topics` are the indexed arguments of the event, and the `data` is the non-indexed arguments. This was glossed over in the first part, but now it will be important. If you take a look at the event definition: ```solidity event Transfer(address indexed from, address indexed to, uint256 value); ``` You see that it has three arguments, two indexed and one non-indexed. However, there are three topics in the event. How do we interpret that? In our case, the first one is the event signature, and the other two are the indexed arguments. Importantly, that is not always the case (it is the case for events that are emitted by Solidity contracts, but not necessarily for other contracts or direct assembly code). Let's now decode the event data. The second event has the following data: ```json "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ] ``` The first topic is the [event signature](https://www.4byte.directory/event-signatures/?bytes_signature=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef), and the other two are the from and to addresses. You can easily see how they are zero-padded to accommodate the whole 32 bytes. Similarly, the event in the first transaction that just minted 1,000,000 token wei (hex encoded in the data field) has the same zeroth topic, same recipient (topic with index 2), and zero address as the sender. {/* */} Let's upgrade the contract from before to tally ERC20 payments on external chains. You can do this by listening to events, decoding them, and using the decoded information. {/* TODO: Here, we will create a simple contract on Sepolia and follow the events it emits, just to see another example of how events function. --> */} ### Decoding transaction data You now know how to listen to events and decode them. Let's see how we can also decode top-level transaction data. Here, you will verify whether the top-level transaction really did increase the ERC20 allowance and see how to get top-level calldata. The full code for this example is in the `scripts/evm/tryERC20Allowance.ts` and `contracts/MintableERC20.sol` files. You initiate a simple `allowance` increase on Sepolia and then decode the calldata to see if it is really what you expect. The example response is something like this: ```json { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1709147568", "requestBody": { "transactionHash": "0x445ac68dd09198cb3b8202cb9ccba323d4d1c82157a076f97fd6682dfaa826d9", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5382600", "timestamp": "1709147568", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xc14FA393fa7248c73B74A303cf35D5e980E11e2C", "value": "0", "input": "0x095ea7b3000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d42900000000000000000000000000000000000000000000000000000000000003e8", "status": "1", "events": [ { "logIndex": "54", "emitterAddress": "0xc14FA393fa7248c73B74A303cf35D5e980E11e2C", "topics": [ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ], "data": "0x00000000000000000000000000000000000000000000000000000000000003e8", "removed": false } ] } } } Result(2) [ '0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429', 1000n ] ``` By now, you should be able to see that the emitted event was the `Approval` event, and the data is the new allowance (with the correct participant addresses in the topics). What we want to take a look at is the `input` field. It contains the calldata of the top-level transaction. Since you know the signature of this method, you can easily decode it and get the result you expect. ### Observing state through events We do not have direct access to state on the other chain, but we can circumvent this using events. If we deploy a contract on the external chain that emits events pertaining to the state it can read (at that block) from the chain, we can easily observe this state (frozen at that point in time) on Flare. Let's see how we can easily observe the current status of ERC20 allowance. The full code for this example is in the `scripts/evm/tryStateChecking.ts` and `contracts/FallbackWithEventContract.sol` files. The contract is simple: ```solidity function getState(address target, bytes calldata cdata) external payable { // Just forward the call to the contract we want to interact with // Caution - this is very unsafe, as the calldata can be anything // If this contract were to had some tokens for example, the calldata could be used to transfer them. (bool result, bytes memory returnData) = target.call{value: msg.value}(cdata); emit CallResult(target, result, msg.data, returnData); // A bit safer way would be to only allow specific functions to be called or use something like this: https://github.com/gnosis/util-contracts/blob/main/contracts/storage/StorageAccessible.sol } ``` Any call to this contract will be forwarded to the target contract, and the result will be emitted as an event. The script is also relatively simple (though it does a lot of things). We get the event in the same way as before, but now we also get the calldata and the target address. We need to do two things: First, decode the event to see what happened, and then decode the calldata to see what the state is. Then, decode both data bytes to see what we got. Importantly, it is necessary to know the structure of the event and the method we called to properly decode it. The response is something like this: ```bash Sepolia USDT deployed to: 0xf274cCf1f92F9B34FF5704802a9B690E1d3cbC38 FallbackWithEventContract deployed to: 0xfCcB55F281df58869593B64B48f8c2Fe66f91C5D { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1709151372", "requestBody": { "transactionHash": "0xff86f77260f7623f24ea888dfd14c56380c5cece1a896bd2566d6b3596343e20", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5382901", "timestamp": "1709151372", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xfCcB55F281df58869593B64B48f8c2Fe66f91C5D", "value": "0", "input": "0xf29ca36c000000000000000000000000f274ccf1f92f9b34ff5704802a9b690e1d3cbc3800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044dd62ed3e0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d42900000000000000000000000000000000000000000000000000000000", "status": "1", "events": [ { "logIndex": "4", "emitterAddress": "0xfCcB55F281df58869593B64B48f8c2Fe66f91C5D", "topics": [ "0xe1b725358090db1f537294b09c773c14622b44c1bc2832d105fb28cc48f5bd90" ], "data": "0x000000000000000000000000f274ccf1f92f9b34ff5704802a9b690e1d3cbc380000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000044dd62ed3e0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d4290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000bc614e", "removed": false } ] } } } Event data [ '0xf274cCf1f92F9B34FF5704802a9B690E1d3cbC38', true, '0xdd62ed3e0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429', '0x0000000000000000000000000000000000000000000000000000000000bc614e' ] Method signature 0xdd62ed3e Decoded calldata Result(2) [ '0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373', '0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429' ] Decoded state data Result(1) [ 12345678n ] ``` We can see that the event was emitted and all the calldata was properly decoded. Why is that important? It means that you can now observe any state on the external blockchain without having to modify the contract on the external blockchain. This allows you to easily observe USDT movements, current token balances, and other state changes on the external blockchain. ### State observation and decoding The last example showed how you can observe the state on another blockchain and use it in TypeScript. Now, we will also see how to properly decode the event in a smart contract. We will use the same contract onchain as before to emit events, `CallResult`, and then decode them in the contract. The result will then be passed to the contract on Coston, which will first decode the full event, ensure that the correct function was called, and then decode the returned data (which is the state you want to observe). The full contract that does this is in `contracts/ERC20BalanceMonitor.sol` and the accompanying script is in the `scripts/evm/tryStateCheckingAndSave.ts` file. What you want to do is simple: query the ERC20 balance of a specific address and save it in the contract storage. Here, you need to be careful, as this query is valid only at the time of the transaction; it might be different at the time of block creation and confirmation. Plus, keep in mind that emitting an event means executing a transaction, and that means gas, so you should be careful with how often you do this. The process is the same as before: you invoke the contract, it emits the event, and you use the result to interact with the chain. But this time, you cheat a bit. Instead of waiting for the whole data connector process to finish, you use `getResponse` to get just the response without the proof. The `ERC20BalanceMonitor` then disregards the proof and just uses the response to process the data. The number of events can be quite large and processing all of them can be tedious (and error-prone), so the easiest way is to find out which event is the one you want and add an index parameter to the function call. The code for this: ```solidity /* The function assumes that the event emitted in the eventIndex is the result of checking the balance of specific ERC20 token as emitted by FallbackWithEventContract (see previous guides). The main idea is to first emit the event checking the balance and then properly decode it */ function confirmBalanceEvent(EVMTransaction.Proof calldata transaction, address tokenAddress, address targetAddress, uint256 eventIndex) public { // We explicitly ignore the proof here, but in production code, you should always verify the proof // We ignore it so we can test the whole contract much faster on the same network using only the // In this guide we will just use the `prepareResponse` endpoint which has everything we need but the proof require( true || isEVMTransactionProofValid(transaction), "Invalid proof" ); EVMTransaction.Event memory _event = transaction.data.responseBody.events[eventIndex]; // This just check the happy path - do kkep in mind, that this can possibly faked // And keep in mind that the specification does not require the topic0 to be event signature require( _event.topics[0] == keccak256("CallResult(address,bool,bytes,bytes)"), "Invalid event" ); // _event.emitterAddress should be the contract we "trust" to correctly call the ERC20 token (address target, bool result, bytes memory callData, bytes memory returnData) = abi.decode( _event.data, (address, bool, bytes, bytes) ); require(target == tokenAddress, "Invalid token address"); bytes memory expectedCalldata = abi.encodeWithSignature("balanceOf(address)", targetAddress); require( keccak256(callData) == keccak256(expectedCalldata), "Invalid calldata" ); // If a tuple was returned from the call, we can unpack it using abi.decode in the same way as in the event data decoding uint256 balance = abi.decode(returnData, (uint256)); balances[transaction.data.responseBody.blockNumber] = BalanceInfo({ holder: targetAddress, token: tokenAddress, amount: balance, blockNumber: transaction.data.responseBody.blockNumber, timestamp: transaction.data.responseBody.timestamp, rawEvent: _event, proofHash: keccak256(abi.encode(transaction)) }); } ``` We just ignore the proof, but then the fun part starts. We get the top-level event out of the response (this is the one that contains calldata and return data), check that the topic matches, and then decode the resulting data. Be careful, decoding the data might fail if you don't have the correct signature, so the example code is fine to show, but you might want to add more checks in production code. Once the data of the top-level event is decoded, we check if the call data is what we expect and then decode the return data to get the balance, which is again dependent on what kind of return value was produced in the transaction. Again, the return data needs to be decoded (it might return something more complicated than just one `uint256`), but it is easy to get the full result. Once you have all this, you just write it to the contract storage, and you are done. Let's take a look at the test code and show a simple trick that is also hidden in there. The code is practically the same as before: you create a transaction, query the data connector, and use the data in the contract. But this time, everything is done on the same (Coston - `testSGB`) network. This makes it a bit easier to test, as you don't need to change the network, but it is a minor thing. It does sound strange (and pointless) to allow the Data Connector to be used on the same network, but the main improvement comes from the top-level relayer coming in the FSP. Once the Data Connector is included in the top-level protocol, any Data Connector data is immediately relayed to externally connected chains via relay (as is the FTSO data). This means that external chains can also observe what is happening on Flare. Think about this: up until now, you only relayed information from other chains to Flare, but now any example from the EVM part can immediately be replicated on the Sepolia chain with Flare being the source chain (where things happen). {/* */} --- ## Create Attestation Type This guide is for developers who want to extend FDC by creating a new attestation type. In this guide, you will learn how to: - Setup a new attestation type definition in Solidity. - Generate a attestation type verifier server in TypeScript with controllers and services. - Create an onchain attestation verifier contract in Solidity. ## Prerequisites Ensure you have the following tools installed: - [yarn](https://yarnpkg.com/) - [Docker](https://www.docker.com) ## Setup type definition 1. **Clone and setup repository:** ```bash git clone https://github.com/flare-foundation/VerifierServerGenerator.git yarn ``` 2. **Create a definition file:** In the root of the cloned repo a template definition file (`ITypeTemplate.sol.example`) is provided. Rename the file to `ICustomType.sol` and the interface defined within the file to to `ICustomType`. You can use any name in place of `CustomType` upto 32 ASCII characters. ```bash mkdir contracts/interfaces/types cp ICustomType.sol contracts/interfaces/types ```
Definition file template {ITypeTemplate}
3. **Modify `@custom` props:** - `@custom:name`: Rename to `@custom:CustomType`. - `@custom:supported`: Indicate the data source, currently supported sources - `BTC`, `DOGE` ,`XRP`, `FLR`, `SGB`, `ETH` and `WEB2`. A single type can support multiple data sources. - `@custom:verification`: Add instructions on how to construct a response from the request. - `@custom:lut`: Leave as default, this will be used later. 4. **Define request and response structs:** Define the inputs to the `RequestBody` and `ResponseBody` structs. All fields should be commented with `@param fieldName` as in the template. ## Generate verifier server 1. **Generate verifier server template:** ```bash yarn generate server -t ICustomType ``` :::warning[Overwriting files] Any changes made to generated files in `server/ICustomType` are overwritten if you call `generate server`. ::: 2. **Define data sources:** - **Single data source:** Change the source in the constructor of `ICustomType.service.ts` to the one specified in the type definition. Modify the `verifyRequest` function to match the verification rules defined by your attestation type. - **Multiple data sources:** Each source needs its own service and controllers. 1. **Create services for each source:** In `ICustomType.service.ts` for each source, create a new class, e.g. `ICustomTypeVerifierService`, that implements `verifyRequest` function for the source. 2. **Create controllers for each source:** In `ICustomType.controller.ts` for each source, create a new class, e.g. `ICustomTypeVerifierController`, and set the type of verifierService to the one created for this type: `@ApiTags('ICustomType')` → `@ApiTags('ICustomType', '')` `@Controller('ICustomType')` → `@Controller('/ICustomType')`. 3. **Add services and controllers to module:** In `ICustomType.module.ts`, add all new services and controllers to the respective arrays. 3. **Generate Dockerfile:** Run the following command to create a Dockerfile inside `server/ICustomType/` that prepares the server to be run inside a Docker container. ```bash yarn build ``` 4. **Build server image:** Run the following to build an image for `ICustomType` ```bash docker build -t library/verifier-indexer-api-CustomType -f server/ICustomType/Dockerfile . ``` 5. **Start server:** Run the following to start the server and expose port `` ```bash docker run --rm --publish :8000 library/verifier-indexer-api-CustomType ``` ## Create verifier contract :::warning[In production] In production use, the verifier contract should validate the response with proof against the Merkle root stored on the [`Relay`](/network/fsp/solidity-reference/IRelay) contract. ::: After completing the last steps a mock verification contract is generated with a `verifyCustomType` method that accepts an attestation response with Merkle proof (see the `Proof` struct in `ICustomType.sol`) and always confirms it. --- ## EVM Connectivity In [Part 1](/fdc/getting-started) and [Part 2](/fdc/attestation-types) of the series, you you have learned how the Data Connector works and what kind of different attestations you can get from it. In this guide, you will: - Move from the world of UTXO chains to the world of EVM chains with a new `EVMTransaction` attestation type. - Understand the implications of connecting account-based chains, and the additional possibilities that smart contracts bring. - Connect Ethereum and Flare (or testnets Sepolia and Flare Testnet Coston2 for the Coston testnet). From Parts 1 and 2, the attestations that you know so far are: - Simple payment - Non-existence of a payment with reference - Balance decreasing transaction - Block height confirmation - Address validity check You also know that the Data Connector allows Flare to connect to Bitcoin, Dogecoin, and XRP Ledger. The information that the Data Connector provides is similar to what was provided before (sender and recipient, amount, block, timestamp, etc.), but since you are on a smart contract compatible chain now, you can also get additional things, namely, you can extract the full data about events that were emitted during the transaction, and you can also get the input data of the transaction (in case a contract was called). ## Transaction Type Let's jump directly into the transaction type to see what kind of data we need to provide. The top-level `Request` in the `EVMTransaction` has the same structure as others: ```solidity title="EVMTransaction.sol" // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name EVMTransaction * @custom:id 0x06 * @custom:supported ETH, FLR, SGB, testETH, testFLR, testSGB * @author Flare * @notice A relay of a transaction from an EVM chain. * This type is only relevant for EVM-compatible chains. * @custom:verification If a transaction with the `transactionId` is in a block on the main branch with at least `requiredConfirmations`, the specified data is relayed. * If an indicated event does not exist, the request is rejected. * @custom:lut `timestamp` */ interface EVMTransaction { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response. * @param requestBody Data defining the request. Type (struct) and interpretation is determined by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the Data Connector round in which the request was considered. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction of the response body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for EVM transaction attestation type * @custom:below Note that events (logs) are indexed in block not in each transaction. The contract that uses the attestation should specify the order of event logs as needed and the requestor should sort `logIndices` * with respect to the set specifications. If possible, the contact should only require one `logIndex`. * @param transactionHash Hash of the transaction(transactionHash). * @param requiredConfirmations The height at which a block is considered confirmed by the requestor. * @param provideInput If true, "input" field is included in the response. * @param listEvents If true, events indicated by `logIndices` are included in the response. Otherwise, no events are included in the response. * @param logIndices If `listEvents` is `false`, this should be an empty list, otherwise, the request is rejected. If `listEvents` is `true`, this is the list of indices (logIndex) of the events to be relayed (sorted by the requestor). The array should contain at most 50 indices. If empty, it indicates all events in order capped by 50. */ struct RequestBody { bytes32 transactionHash; uint16 requiredConfirmations; bool provideInput; bool listEvents; uint32[] logIndices; } /** * @notice Response body for EVM transaction attestation type * @custom:below The fields are in line with [transaction](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash) provided by EVM node. * @param blockNumber Number of the block in which the transaction is included. * @param timestamp Timestamp of the block in which the transaction is included. * @param sourceAddress The address (from) that signed the transaction. * @param isDeployment Indicate whether it is a contract creation transaction. * @param receivingAddress The address (to) of the receiver of the initial transaction. Zero address if `isDeployment` is `true`. * @param value The value transferred by the initial transaction in wei. * @param input If `provideInput`, this is the data send along with the initial transaction. Otherwise it is the default value `0x00`. * @param status Status of the transaction 1 - success, 0 - failure. * @param events If `listEvents` is `true`, an array of the requested events. Sorted by the logIndex in the same order as `logIndices`. Otherwise, an empty array. */ struct ResponseBody { uint64 blockNumber; uint64 timestamp; address sourceAddress; bool isDeployment; address receivingAddress; uint256 value; bytes input; uint8 status; Event[] events; } /** * @notice Event log record * @custom:above An `Event` is a struct with the following fields: * @custom:below The fields are in line with [EVM event logs](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getfilterchanges). * @param logIndex The consecutive number of the event in block. * @param emitterAddress The address of the contract that emitted the event. * @param topics An array of up to four 32-byte strings of indexed log arguments. * @param data Concatenated 32-byte strings of non-indexed log arguments. At least 32 bytes long. * @param removed It is `true` if the log was removed due to a chain reorganization and `false` if it is a valid log. */ struct Event { uint32 logIndex; address emitterAddress; bytes32[] topics; bytes data; bool removed; } } ``` The `attestationType` for the evm attestation is a hex encoding of `hexEncode("EVMTransaction")` {/* Currently supported `sourceId` are */} {/* - TODO: Naštej jih in prveri, da delajo ok */} ## Request Body The `RequestBody` is defined as: ```solidity /** * @notice Request body for EVM transaction attestation type * @custom:below Note that events (logs) are indexed in block not in each transaction. The contract that uses the attestation should specify the order of event logs as needed and the requestor should sort `logIndices` * with respect to the set specifications. If possible, the contact should only require one `logIndex`. * @param transactionHash Hash of the transaction(transactionHash). * @param requiredConfirmations The height at which a block is considered confirmed by the requestor. * @param provideInput If true, "input" field is included in the response. * @param listEvents If true, events indicated by `logIndices` are included in the response. Otherwise, no events are included in the response. * @param logIndices If `listEvents` is `false`, this should be an empty list, otherwise, the request is rejected. If `listEvents` is `true`, this is the list of indices (logIndex) of the events to be relayed (sorted by the requestor). The array should contain at most 50 indices. If empty, it indicates all events in order capped by 50. */ struct RequestBody { bytes32 transactionHash; uint16 requiredConfirmations; bool provideInput; bool listEvents; uint32[] logIndices; } ``` - `TransactionHash`: Hash of the transaction you are observing. - `RequiredConfirmations`: The number of blocks after the transaction that you are requesting must be visible to the attestation client to consider this transaction as finalized. Unlike the previous payment (or block height) attestation, where the amount of block confirmations was set per chain, this type is more flexible and allows you to choose how many confirmations you want, thus adapting your security assumptions (about the other chain). - `provideInput`: Indicates if the response should also contain the input of the transaction. You can always include the input, but this might produce a large data structure that you will need to supply when using this transaction. If you don't need this data, it is advisable not to include it to avoid additional gas costs both for supplying it to the verification contract and making a transaction. However, it might be useful, for example, to check what contract was deployed or what was the top-level method that was executed. - `listEvents`: Events are an important and powerful tool when interacting with EVM chains, but including them adds additional costs (the same as with input). If you don't need events, you can save some gas costs by excluding them. - `logIndices`: An array of log indices (in any order, with repetitions allowed) for which events (logs) you want included as the result of your transaction attestation. As before, don't include events you don't need for gas reasons. Importantly, leaving this array empty will include all events emitted in the same order as they were emitted. The indices are the block log indices, indicating the event index in the whole block (not just the transactions you are attesting to), but if you supply an index outside your transaction range, the corresponding event won't be included. The amount of returned events is limited to 50, so if you want to attest that you have included all the events in a single transaction, make sure it has 49 of them or less. {/* () */} ## Response Body The `ResponseBody` is defined as: ```solidity /** * @notice Response body for EVM transaction attestation type * @custom:below The fields are in line with [transaction](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash) provided by EVM node. * @param blockNumber Number of the block in which the transaction is included. * @param timestamp Timestamp of the block in which the transaction is included. * @param sourceAddress The address (from) that signed the transaction. * @param isDeployment Indicate whether it is a contract creation transaction. * @param receivingAddress The address (to) of the receiver of the initial transaction. Zero address if `isDeployment` is `true`. * @param value The value transferred by the initial transaction in wei. * @param input If `provideInput`, this is the data send along with the initial transaction. Otherwise it is the default value `0x00`. * @param status Status of the transaction 1 - success, 0 - failure. * @param events If `listEvents` is `true`, an array of the requested events. Sorted by the logIndex in the same order as `logIndices`. Otherwise, an empty array. */ struct ResponseBody { uint64 blockNumber; uint64 timestamp; address sourceAddress; bool isDeployment; address receivingAddress; uint256 value; bytes input; uint8 status; Event[] events; } /** * @notice Event log record * @custom:above An `Event` is a struct with the following fields: * @custom:below The fields are in line with [EVM event logs](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getfilterchanges). * @param logIndex The consecutive number of the event in block. * @param emitterAddress The address of the contract that emitted the event. * @param topics An array of up to four 32-byte strings of indexed log arguments. * @param data Concatenated 32-byte strings of non-indexed log arguments. At least 32 bytes long. * @param removed It is `true` if the log was removed due to a chain reorganization and `false` if it is a valid log. */ struct Event { uint32 logIndex; address emitterAddress; bytes32[] topics; bytes data; bool removed; } ``` The response body struct contains the following fields: - `blockNumber`: Number of the block in which the transaction is included. - `timestamp`: Timestamp of the block the transaction was included in. - `sourceAddress`: Address signing the transaction. Since Flare is an EVM chain, this is nicely mapped to the address type directly, and you don't have to operate with strings or address hashes. - `isDeployment`: Flag indicating if this transaction was a contract deployment. - `receivingAddress`: The `to` address of the transaction (this is a zero address if you are dealing with contract deployment). Keep in mind, this can also be a contract address (if the toplevel transaction is a contract call) and this is where things get interesting. - `value`: The value (in wei) transferred by the toplevel transaction. Values transferred by internal transactions are not tracked by this type, but if proper events are emitted you can use them to follow this. If there is no value, the value has a default `0` value. - `input`: The input provided with a transaction (useful for contract calls). If no input is provided, a default value of zero bytes is used. - `status`: The status of the transaction, which can either be `1` indicating success or `0` indicating failure (without failure reason). - `events`: Array of requested events in the same order as requested. Each event has the following fields: - `logIndex`: The consecutive number of the event in the block. - `emitterAddress`: The address of the contract that emitted the event. - `topics`: An array of up to four 32-byte strings of indexed log arguments. - `data`: Concatenated 32-byte strings of non-indexed log arguments. This (together with topics) is usually the part of an event that you will decode to get the information you need. Keep in mind, this is event-specific and you will need to know the event structure to decode it properly. - `removed`: It is `true` if the log was removed due to a chain reorganization (transaction was mined, but the block was not on the main chain) and `false` if it is a valid log. ## Examples Now that you know how to request an attestation and what you are getting in return, let's explore some examples. These examples are a bit more involved and each will come in a few parts: - Script making a dummy transaction on the `Sepolia` testnet. - Smart contract(s) accepting an attestation request and performing some desired action. - Deployment and run script that ties them together. This deployment script will also allow you to understand exactly how long the waiting for each phase takes, which is something not previously focused on. ### Simple transaction with a value Let's start small. You will create a smart contract that just tallies the top-level amounts transferred to a designated address on Sepolia. The scenario is pretty simple: - You have a "payment" to an Externally Owned Account (EOA - so not a smart contract) on Sepolia, and anyone can send funds there and prove this. - On the Flare side, you will deploy a contract that will accept proofs with data in the proper accounting format: who has sent how much to this end owner address. The full code for this example is in the `scripts/evm/trySimpleTransaction.ts`, `contracts/EthereumPaymentCollector.sol`, and `contracts/FallbackContract` files. You won't be copy-pasting the full code here, but you will go through the most important parts. The setup is now in two parts, and `main` correctly picks up the right part to run depending on the network it is run on. First, deploy a simple `FallbackContract` on Sepolia. ```bash yarn hardhat run scripts/evm/trySimpleTransaction --network sepolia ``` - This contract will just emit an event when the `fallback` function is called. - You will be attesting to this event in the next part. - The script makes two transactions on Sepolia: one with value to an address and one to the address of the contract. - The second transaction will call the `fallback` function and emit the event. - The transaction hashes are logged, and the JSON response of the attestation results is printed (so you can see what you will get in the next part). Here is an example result: ```bash 0xac640ab047aa1097ddd473e5940921eb500a9912b33072b8532617692428830e { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708907688", "requestBody": { "transactionHash": "0xac640ab047aa1097ddd473e5940921eb500a9912b33072b8532617692428830e", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5363670", "timestamp": "1708907688", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429", "value": "10", "input": "0x0123456789", "status": "1", "events": [] } } } 0x7eb54cde238fc700be31c98af7e4df8c4fc96fd5c634c490183ca612a481efcc { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708907712", "requestBody": { "transactionHash": "0x7eb54cde238fc700be31c98af7e4df8c4fc96fd5c634c490183ca612a481efcc", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5363672", "timestamp": "1708907712", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "value": "10", "input": "0x9876543210", "status": "1", "events": [ { "logIndex": "160", "emitterAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "topics": [ "0xaca09dd456ca888dccf8cc966e382e6e3042bb7e4d2d7815015f844edeafce42" ], "data": "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000059876543210000000000000000000000000000000000000000000000000000000", "removed": false } ] } } } ``` After you have the transaction hashes, copy them to the part of the `main` method that will execute the Data Connector part, this time on Coston. Let's take a look at `executeStateConnectorProof`. Here, the Data Connector part comes into play. You have already seen it in the previous guides, so you will just quickly scan through it. The code is a bit more involved, as you are now working with multiple transactions (this is not EVMTransaction specific, but it is a good example of how you can use the Data Connector to do more complex things). Again, you get an encoded attestation request (one for each transaction) and then you submit them to the Data Connector. Once this is done, you wait for the round to be confirmed (see the while loop that takes most of the time) and then you get the proof. The `EthereumPaymentCollector` contract is deployed on Coston with one important method `collectPayment`. This method accepts the `EVMTransaction.Proof` response and does the important accounting. As usual, first check that the provided proof is correct: that the Merkle proof really attests that this transaction was included in the Merkle tree. Then comes the fun part - you can use the information from a transaction to do whatever you want. You won't just write it to the list of all transactions and be done. Instead, you will try to decode the event data and see what you can get from it. As mentioned before, the event data is specific to the event and you need to know the event structure to decode it properly. In this case, you know how it looks, and the decoding is done by the built-in `abi.decode`. You then just push the decoded data in struct form to the list of events and you are done. :::warning `abi.decode` is not type-safe and you can easily get wrong results if you don't know the event structure. Even more, this might be a security risk if you are not careful (or revert unexpectedly), but it is a nice representation of how powerful the events - and their information - can be. ::: Finally, when you have both proofs and the contract deployed, you just call the `collectPayment` method with the proofs, and you are done (unless something goes wrong, then you will have to wait for the next round and try again). The result looks something like: ```bash Rounds: [ '809307', '809307' ] Waiting for the round to be confirmed 809303n 809307 Waiting for the round to be confirmed 809303n 809307 Waiting for the round to be confirmed 809303n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809304n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809305n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Waiting for the round to be confirmed 809306n 809307 Round confirmed, getting proof Successfully submitted source code for contract contracts/EthereumPaymentCollector.sol:EthereumPaymentCollector at 0x7cf6E7aeFD0207a5bE9a7DbcDA560fc7a6dBD7B4 for verification on the block explorer. Waiting for verification result... Successfully verified contract EthereumPaymentCollector on the block explorer. https://coston-explorer.flare.network/address/0x7cf6E7aeFD0207a5bE9a7DbcDA560fc7a6dBD7B4#code { "data": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "lowestUsedTimestamp": "1708907688", "requestBody": { "listEvents": true, "logIndices": [], "provideInput": true, "requiredConfirmations": "1", "transactionHash": "0xac640ab047aa1097ddd473e5940921eb500a9912b33072b8532617692428830e" }, "responseBody": { "blockNumber": "5363670", "events": [], "input": "0x0123456789", "isDeployment": false, "receivingAddress": "0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "status": "1", "timestamp": "1708907688", "value": "10" }, "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "809307" }, "merkleProof": [ "0x56faf895bbcb0b2a6f3bc283ea5e1793b224dca8b4b99240a34cee6d9bf1b8f3", "0x13ef0de709e7b0485f7623f5a0ad5b56aa23626fbffe5e7f4502bb7be5e0bf7e", "0xf72c31824174676516a9c5d9713cb1ae8866cac71462fe2b1a3c1e1b9418a94f" ] } { "data": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "lowestUsedTimestamp": "1708907712", "requestBody": { "listEvents": true, "logIndices": [], "provideInput": true, "requiredConfirmations": "1", "transactionHash": "0x7eb54cde238fc700be31c98af7e4df8c4fc96fd5c634c490183ca612a481efcc" }, "responseBody": { "blockNumber": "5363672", "events": [ { "data": "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000059876543210000000000000000000000000000000000000000000000000000000", "emitterAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "logIndex": "160", "removed": false, "topics": [ "0xaca09dd456ca888dccf8cc966e382e6e3042bb7e4d2d7815015f844edeafce42" ] } ], "input": "0x9876543210", "isDeployment": false, "receivingAddress": "0xeBBf567beDe2D8842dF538Cf64E0bE9976183853", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "status": "1", "timestamp": "1708907712", "value": "10" }, "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "809307" }, "merkleProof": [ "0x8e45d2d564bf7d652cf904a72e53f5e7e34d7e5e184906afda92f755e99cd421", "0x13ef0de709e7b0485f7623f5a0ad5b56aa23626fbffe5e7f4502bb7be5e0bf7e", "0xf72c31824174676516a9c5d9713cb1ae8866cac71462fe2b1a3c1e1b9418a94f" ] } ``` :::info On the previous attestation types, we were only able to get transactions in the last two days (this is attestation type specific). ::: ### Decoding emitted events As previously stated, an event will be the core feature for observing what is happening on other chains. Let's now use this to prove that an ERC20 payment was made on Sepolia and then decode the event to see who made the payment and how much. As before, you will deploy an ERC20 contract on Sepolia, mint some tokens, and send them to an address. The full code is available in the `scripts/evm/tryERC20transfers.ts` and `contracts/MintableERC20.sol` files. A sample response for the ERC20 transaction would look like: ```bash Sepolia USDT deployed to: 0x6023e19d70C304eA16a3728ceDcb042791737EC3 0xd7eed8cf377a4079718e8d709b3648d62a3a16ea39fbfbe759600c3d574caa15 { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708999068", "requestBody": { "transactionHash": "0xd7eed8cf377a4079718e8d709b3648d62a3a16ea39fbfbe759600c3d574caa15", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5370899", "timestamp": "1708999068", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "value": "0", "input": "0x40c10f190000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f37300000000000000000000000000000000000000000000000000000000000f4240", "status": "1", "events": [ { "logIndex": "38", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373" ], "data": "0x00000000000000000000000000000000000000000000000000000000000f4240", "removed": false } ] } } } 0x9dffa80b6daea45ed4bfc93bb72cdb893549fdefb81cb760b7ce08edef9859a6 { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708999080", "requestBody": { "transactionHash": "0x9dffa80b6daea45ed4bfc93bb72cdb893549fdefb81cb760b7ce08edef9859a6", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5370900", "timestamp": "1708999080", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "value": "0", "input": "0xa9059cbb000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d42900000000000000000000000000000000000000000000000000000000000003e8", "status": "1", "events": [ { "logIndex": "32", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ], "data": "0x00000000000000000000000000000000000000000000000000000000000003e8", "removed": false } ] } } } ``` Let's now decode the data you got back and explore the event in a little more detail. ```json { "logIndex": "38", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373" ], "data": "0x00000000000000000000000000000000000000000000000000000000000f4240", "removed": false } { "logIndex": "32", "emitterAddress": "0x6023e19d70C304eA16a3728ceDcb042791737EC3", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ], "data": "0x00000000000000000000000000000000000000000000000000000000000003e8", "removed": false } ``` When processing the events, it is important to know which contract should be emitting the event (you don't want to count a memecoin transfer as a USDT transfer). The `topics` are the indexed arguments of the event, and the `data` is the non-indexed arguments. This was glossed over in the first part, but now it will be important. If you take a look at the event definition: ```solidity event Transfer(address indexed from, address indexed to, uint256 value); ``` You see that it has three arguments, two indexed and one non-indexed. However, there are three topics in the event. How do we interpret that? In our case, the first one is the event signature, and the other two are the indexed arguments. Importantly, that is not always the case (it is the case for events that are emitted by Solidity contracts, but not necessarily for other contracts or direct assembly code). Let's now decode the event data. The second event has the following data: ```json "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ] ``` The first topic is the [event signature](https://www.4byte.directory/event-signatures/?bytes_signature=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef), and the other two are the from and to addresses. You can easily see how they are zero-padded to accommodate the whole 32 bytes. Similarly, the event in the first transaction that just minted 1,000,000 token wei (hex encoded in the data field) has the same zeroth topic, same recipient (topic with index 2), and zero address as the sender. {/* */} Let's upgrade the contract from before to tally ERC20 payments on external chains. You can do this by listening to events, decoding them, and using the decoded information. {/* TODO: Here, we will create a simple contract on Sepolia and follow the events it emits, just to see another example of how events function. --> */} ### Decoding top-level transaction data You now know how to listen to events and decode them. Let's see how we can also decode top-level transaction data. Here, you will verify whether the top-level transaction really did increase the ERC20 allowance and see how to get top-level calldata. The full code for this example is in the `scripts/evm/tryERC20Allowance.ts` and `contracts/MintableERC20.sol` files. You initiate a simple `allowance` increase on Sepolia and then decode the calldata to see if it is really what you expect. The example response is something like this: ```json { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1709147568", "requestBody": { "transactionHash": "0x445ac68dd09198cb3b8202cb9ccba323d4d1c82157a076f97fd6682dfaa826d9", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5382600", "timestamp": "1709147568", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xc14FA393fa7248c73B74A303cf35D5e980E11e2C", "value": "0", "input": "0x095ea7b3000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d42900000000000000000000000000000000000000000000000000000000000003e8", "status": "1", "events": [ { "logIndex": "54", "emitterAddress": "0xc14FA393fa7248c73B74A303cf35D5e980E11e2C", "topics": [ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "0x0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373", "0x000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429" ], "data": "0x00000000000000000000000000000000000000000000000000000000000003e8", "removed": false } ] } } } Result(2) [ '0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429', 1000n ] ``` By now, you should be able to see that the emitted event was the `Approval` event, and the data is the new allowance (with the correct participant addresses in the topics). What we want to take a look at is the `input` field. It contains the calldata of the top-level transaction. Since you know the signature of this method, you can easily decode it and get the result you expect. ### Observing State Through Events We do not have direct access to state on the other chain, but we can circumvent this using events. If we deploy a contract on the external chain that emits events pertaining to the state it can read (at that block) from the chain, we can easily observe this state (frozen at that point in time) on Flare. Let's see how we can easily observe the current status of ERC20 allowance. The full code for this example is in the `scripts/evm/tryStateChecking.ts` and `contracts/FallbackWithEventContract.sol` files. The contract is simple: ```solidity function getState(address target, bytes calldata cdata) external payable { // Just forward the call to the contract we want to interact with // Caution - this is very unsafe, as the calldata can be anything // If this contract were to had some tokens for example, the calldata could be used to transfer them. (bool result, bytes memory returnData) = target.call{value: msg.value}(cdata); emit CallResult(target, result, msg.data, returnData); // A bit safer way would be to only allow specific functions to be called or use something like this: https://github.com/gnosis/util-contracts/blob/main/contracts/storage/StorageAccessible.sol } ``` Any call to this contract will be forwarded to the target contract, and the result will be emitted as an event. The script is also relatively simple (though it does a lot of things). We get the event in the same way as before, but now we also get the calldata and the target address. We need to do two things: First, decode the event to see what happened, and then decode the calldata to see what the state is. Then, decode both data bytes to see what we got. Importantly, it is necessary to know the structure of the event and the method we called to properly decode it. The response is something like this: ```bash Sepolia USDT deployed to: 0xf274cCf1f92F9B34FF5704802a9B690E1d3cbC38 FallbackWithEventContract deployed to: 0xfCcB55F281df58869593B64B48f8c2Fe66f91C5D { "status": "VALID", "response": { "attestationType": "0x45564d5472616e73616374696f6e000000000000000000000000000000000000", "sourceId": "0x7465737445544800000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1709151372", "requestBody": { "transactionHash": "0xff86f77260f7623f24ea888dfd14c56380c5cece1a896bd2566d6b3596343e20", "requiredConfirmations": "1", "provideInput": true, "listEvents": true, "logIndices": [] }, "responseBody": { "blockNumber": "5382901", "timestamp": "1709151372", "sourceAddress": "0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373", "isDeployment": false, "receivingAddress": "0xfCcB55F281df58869593B64B48f8c2Fe66f91C5D", "value": "0", "input": "0xf29ca36c000000000000000000000000f274ccf1f92f9b34ff5704802a9b690e1d3cbc3800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044dd62ed3e0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d42900000000000000000000000000000000000000000000000000000000", "status": "1", "events": [ { "logIndex": "4", "emitterAddress": "0xfCcB55F281df58869593B64B48f8c2Fe66f91C5D", "topics": [ "0xe1b725358090db1f537294b09c773c14622b44c1bc2832d105fb28cc48f5bd90" ], "data": "0x000000000000000000000000f274ccf1f92f9b34ff5704802a9b690e1d3cbc380000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000044dd62ed3e0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d4290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000bc614e", "removed": false } ] } } } Event data [ '0xf274cCf1f92F9B34FF5704802a9B690E1d3cbC38', true, '0xdd62ed3e0000000000000000000000004c3dfafc3207eabb7dc8a6ab01eb142c8655f373000000000000000000000000ff02f742106b8a25c26e65c1f0d66bec3c90d429', '0x0000000000000000000000000000000000000000000000000000000000bc614e' ] Method signature 0xdd62ed3e Decoded calldata Result(2) [ '0x4C3dFaFc3207Eabb7dc8A6ab01Eb142C8655F373', '0xFf02F742106B8a25C26e65C1f0d66BEC3C90d429' ] Decoded state data Result(1) [ 12345678n ] ``` We can see that the event was emitted and all the calldata was properly decoded. Why is that important? It means that you can now observe any state on the external blockchain without having to modify the contract on the external blockchain. This allows you to easily observe USDT movements, current token balances, and other state changes on the external blockchain. ### State Observation and Decoding The last example showed how you can observe the state on another blockchain and use it in TypeScript. Now, we will also see how to properly decode the event in a smart contract. We will use the same contract onchain as before to emit events, `CallResult`, and then decode them in the contract. The result will then be passed to the contract on Coston, which will first decode the full event, ensure that the correct function was called, and then decode the returned data (which is the state you want to observe). The full contract that does this is in `contracts/ERC20BalanceMonitor.sol` and the accompanying script is in the `scripts/evm/tryStateCheckingAndSave.ts` file. What you want to do is simple: query the ERC20 balance of a specific address and save it in the contract storage. Here, you need to be careful, as this query is valid only at the time of the transaction; it might be different at the time of block creation and confirmation. Plus, keep in mind that emitting an event means executing a transaction, and that means gas, so you should be careful with how often you do this. The process is the same as before: you invoke the contract, it emits the event, and you use the result to interact with the chain. But this time, you cheat a bit. Instead of waiting for the whole data connector process to finish, you use `getResponse` to get just the response without the proof. The `ERC20BalanceMonitor` then disregards the proof and just uses the response to process the data. The number of events can be quite large and processing all of them can be tedious (and error-prone), so the easiest way is to find out which event is the one you want and add an index parameter to the function call. The code for this: ```solidity /* The function assumes that the event emitted in the eventIndex is the result of checking the balance of specific ERC20 token as emitted by FallbackWithEventContract (see previous guides). The main idea is to first emit the event checking the balance and then properly decode it */ function confirmBalanceEvent(EVMTransaction.Proof calldata transaction, address tokenAddress, address targetAddress, uint256 eventIndex) public { // We explicitly ignore the proof here, but in production code, you should always verify the proof // We ignore it so we can test the whole contract much faster on the same network using only the // In this guide we will just use the `prepareResponse` endpoint which has everything we need but the proof require( true || isEVMTransactionProofValid(transaction), "Invalid proof" ); EVMTransaction.Event memory _event = transaction.data.responseBody.events[eventIndex]; // This just check the happy path - do kkep in mind, that this can possibly faked // And keep in mind that the specification does not require the topic0 to be event signature require( _event.topics[0] == keccak256("CallResult(address,bool,bytes,bytes)"), "Invalid event" ); // _event.emitterAddress should be the contract we "trust" to correctly call the ERC20 token (address target, bool result, bytes memory callData, bytes memory returnData) = abi.decode( _event.data, (address, bool, bytes, bytes) ); require(target == tokenAddress, "Invalid token address"); bytes memory expectedCalldata = abi.encodeWithSignature("balanceOf(address)", targetAddress); require( keccak256(callData) == keccak256(expectedCalldata), "Invalid calldata" ); // If a tuple was returned from the call, we can unpack it using abi.decode in the same way as in the event data decoding uint256 balance = abi.decode(returnData, (uint256)); balances[transaction.data.responseBody.blockNumber] = BalanceInfo({ holder: targetAddress, token: tokenAddress, amount: balance, blockNumber: transaction.data.responseBody.blockNumber, timestamp: transaction.data.responseBody.timestamp, rawEvent: _event, proofHash: keccak256(abi.encode(transaction)) }); } ``` We just ignore the proof, but then the fun part starts. We get the top-level event out of the response (this is the one that contains calldata and return data), check that the topic matches, and then decode the resulting data. Be careful, decoding the data might fail if you don't have the correct signature, so the example code is fine to show, but you might want to add more checks in production code. Once the data of the top-level event is decoded, we check if the call data is what we expect and then decode the return data to get the balance, which is again dependent on what kind of return value was produced in the transaction. Again, the return data needs to be decoded (it might return something more complicated than just one `uint256`), but it is easy to get the full result. Once you have all this, you just write it to the contract storage, and you are done. Let's take a look at the test code and show a simple trick that is also hidden in there. The code is practically the same as before: you create a transaction, query the data connector, and use the data in the contract. But this time, everything is done on the same (Coston - `testSGB`) network. This makes it a bit easier to test, as you don't need to change the network, but it is a minor thing. It does sound strange (and pointless) to allow the Data Connector to be used on the same network, but the main improvement comes from the top-level relayer coming in the FSP. Once the Data Connector is included in the top-level protocol, any Data Connector data is immediately relayed to externally connected chains via relay (as is the FTSO data). This means that external chains can also observe what is happening on Flare. Think about this: up until now, you only relayed information from other chains to Flare, but now any example from the EVM part can immediately be replicated on the Sepolia chain with Flare being the source chain (where things happen). {/* */} --- ## Json Api :::danger Since May 2025 this guide is considered deprecated. The `JsonApi` attestation type has been update to a new version, `Web2Json`. You can find the updated version of this guide [here](/fdc/guides/foundry/web2-json). ::: The `JsonApi` attestation type enables data collection from an arbitrary Web2 source. You can learn more about it in the official [specification repo](/fdc/attestation-types/json-api). We will now demonstrate how the FDC protocol can be used to collect the data of a given [Star Wars API](https://swapi.dev/) request. The request we will be making is `https://swapi.dev/api/peaople/3/`. The same procedure works for all public APIs. In this guide, we will follow the steps outlined in the [FDC overview](/fdc/overview). Our implementation requires handling the FDC voting round finalization process. To manage this, we will create separate scripts in `script/fdcExample/JsonApi.s.sol` that handle different stages of the validation process: ```solidity title="script/fdcExample/JsonApi.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "JsonApi"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { ... } contract SubmitAttestationRequest is Script { ... } contract RetrieveDataAndProof is Script { ... } contract Deploy is Script { ... } ... ``` The names of included contracts mostly mirror the steps described in the [FDC guide](/fdc/overview). To bridge the separate executions of the scripts, we will save the relevant data of each script to a file in the `dirPath` folder. Each succeeding script will then read that file to load the data. ## Prepare request The JSON request to the verifier is the same form for all attestation types, but the values of the fields differ between them. It contains the following fields. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `JsonApi`, `requestBody` is a JSON containing the fields: - `url`: url of the data source; as `string` - `postprocessJq`: JQ filter to postprocess the json data received from the URL; as `string` - `abi_signature`: ABI signature of the Solidity struct that will be used to decode the data; as `string` ### Reference Documentation - [JsonApi Specification](/fdc/attestation-types/json-api) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc#/) ### Example Values - `url`: the above address `https://swapi.dev/api/people/3/` - `postprocessJq`: `{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split(\\"/\\") | .[-2] | tonumber)}` - `abi_signature`: ```bash {\\"components\\": [ {\\"internalType\\": \\"string\\", \\"name\\": \\"name\\", \\"type\\": \\"string\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"height\\", \\"type\\": \\"uint256\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"mass\\", \\"type\\": \\"uint256\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"numberOfFilms\\", \\"type\\": \\"uint256\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"uid\\", \\"type\\": \\"uint256\\"} ], \\"name\\": \\"task\\",\\"type\\": \\"tuple\\"} ``` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHexString`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) within the [example repository](https://github.com/flare-foundation/flare-foundry-starter), but they can also be defined locally in your contract or script. ```solidity title="scrip/fdcExample/Base.s.sol" function toHexString( bytes memory data ) public pure returns (string memory) { bytes memory alphabet = "0123456789abcdef"; bytes memory str = new bytes(2 + data.length * 2); str[0] = "0"; str[1] = "x"; for (uint i = 0; i < data.length; i++) { str[2 + i * 2] = alphabet[uint(uint8(data[i] >> 4))]; str[3 + i * 2] = alphabet[uint(uint8(data[i] & 0x0f))]; } return string(str); } ``` ```solidity title="script/fdcExample/Base.s.sol" function toUtf8HexString( string memory _string ) internal pure returns (string memory) { string memory encodedString = toHexString( abi.encodePacked(_string) ); uint256 stringLength = bytes(encodedString).length; require(stringLength <= 64, "String too long"); uint256 paddingLength = 64 - stringLength + 2; for (uint256 i = 0; i < paddingLength; i++) { encodedString = string.concat(encodedString, "0"); } return encodedString; } ``` We also define a helper function for formatting data into a JSON string. ```solidity title="scrip/fdcExample/Base.s.sol" function prepareAttestationRequest( string memory attestationType, string memory sourceId, string memory requestBody ) internal view returns (string[] memory, string memory) { // We read the API key from the .env file string memory apiKey = vm.envString("VERIFIER_API_KEY"); // Preparing headers string[] memory headers = prepareHeaders(apiKey); // Preparing body string memory body = prepareBody( attestationType, sourceId, requestBody ); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); console.log("body: %s\n", body); return (headers, body); } function prepareHeaders( string memory apiKey ) internal pure returns (string[] memory) { string[] memory headers = new string[](2); headers[0] = string.concat('"X-API-KEY": ', apiKey); headers[1] = '"Content-Type": "application/json"'; return headers; } function prepareBody( string memory attestationType, string memory sourceId, string memory body ) internal pure returns (string memory) { return string.concat( '{"attestationType": ', '"', attestationType, '"', ', "sourceId": ', '"', sourceId, '"', ', "requestBody": ', body, "}" ); } ``` In the example repository, these are once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/script/fdcExample/Base.s.sol) library file. Thus, the part of the script that prepares the verifier request looks like: ```solidity title="scrip/fdcExample/JsonApi.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... string constant attestationTypeName = "JsonApi"; string constant dirPath = "data/"; contract PrepareAttestationRequest is Script { using Surl for *; // Setting request data string public apiUrl = "https://swapi.dev/api/people/3/"; string public postprocessJq = '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split(\\"/\\") | .[-2] | tonumber)}'; string publicAbiSignature = '{\\"components\\": [' '{\\"internalType\\": \\"string\\", \\"name\\": \\"name\\", \\"type\\": \\"string\\"},' '{\\"internalType\\": \\"uint256\\", \\"name\\": \\"height\\", \\"type\\": \\"uint256\\"},' '{\\"internalType\\": \\"uint256\\", \\"name\\": \\"mass\\", \\"type\\": \\"uint256\\"},' '{\\"internalType\\": \\"uint256\\", \\"name\\": \\"numberOfFilms\\", \\"type\\": \\"uint256\\"},' '{\\"internalType\\": \\"uint256\\", \\"name\\": \\"uid\\", \\"type\\": \\"uint256\\"}' "]," '\\"name\\": \\"task\\",\\"type\\": \\"tuple\\"}'; string public sourceName = "WEB2"; function prepareRequestBody( string memory url, string memory postprocessJq, string memory publicAbiSignature ) private pure returns (string memory) { return string.concat( '{"url": "', url, '","postprocessJq": "', postprocessJq, '","abi_signature": "', publicAbiSignature, '"}' ); } function run() external { // Preparing request data string memory attestationType = Base.toUtf8HexString( attestationTypeName ); string memory sourceId = Base.toUtf8HexString(sourceName); string memory requestBody = prepareRequestBody( apiUrl, postprocessJq, publicAbiSignature ); (string[] memory headers, string memory body) = prepareAttestationRequest(attestationType, sourceId, requestBody); ... } } ... ``` The code above differs slightly from the [starter example](https://github.com/flare-foundation/flare-foundry-starter). But, if we remove the ellipses `...` signifying missing code, we can still run the script. Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/btc/api-doc#/JsonApi/BTCJsonApiVerifierController_prepareRequest) to check the response. We can run the script by calling the following commands in the console. ```bash source .env ``` ```bash forge script script/fdcExample/JsonApi.s.sol:PrepareAttestationRequest --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` The prerequisite for this is that the `.env` file is not missing the `PRIVATE KEY` and `COSTON2_RPC_URL` values. The script can also access other chains; that can be achieved by replacing the `--rpc-url` value with `COSTON_RPC_URL`, `FLARE_RPC_URL`, or `SONGBIRD_RPC_URL`. ## Post request to verifier To post a request to a verifier server, we use the `surl` package. We place `using Surl for *;` at the start of our `PostRequest` contract, and then call its `post` method on the verifier URL. ```solidity title="scrip/fdcExample/JsonApi.s.sol" (, bytes memory data) = url.post(headers, body); ``` We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/` the path `verifier/btc/JsonApi/prepareRequest`. We can do so dynamically with the following code. ```solidity title="scrip/fdcExample/JsonApi.s.sol" string memory baseUrl = "https://fdc-verifiers-testnet.flare.network/"; string memory url = string.concat( baseUrl, "verifier/", baseSourceName, "/", attestationTypeName, "/prepareRequest" ); console.log("url: %s", url); string memory requestBody = string.concat( '{"addressStr": "', addressStr, '"}' ); ``` Lastly, we parse the return data from the verifier server. Using the Foundry `parseJson` shortcode, and a custom struct `AttestationResponse`, we decode the returned data and extract from it the ABI encoded request. ```solidity title="scrip/fdcExample/Base.s.sol" function parseAttestationRequest( bytes memory data ) internal pure returns (AttestationResponse memory) { string memory dataString = string(data); bytes memory dataJson = vm.parseJson(dataString); AttestationResponse memory response = abi.decode( dataJson, (AttestationResponse) ); console.log("response status: %s\n", response.status); console.log("response abiEncodedRequest: "); console.logBytes(response.abiEncodedRequest); console.log("\n"); return response; } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 494a736f6e417069000000000000000000000000000000000000000000000000 5745423200000000000000000000000000000000000000000000000000000000 0b62b2fe7066a5b56cd4cc859f4c802a02e2a0f84b5ad12893ef5a90651e588c 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000060 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000140 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e6465762f6170692f70656f706c652f332f00 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d325d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("JsonApi")` - **Second line:** `toUtf8HexString("testETH")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `JsonApi.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
We write the `abiEncodedRequest` to a file (`data/JsonApi_abiEncodedRequest.txt`) to it in the next step. ```solidity title="scrip/fdcExample/JsonApi.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_abiEncodedRequest"), StringsBase.toHexString(response.abiEncodedRequest), true ); ``` ## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. Now, we submit the validated request to the blockchain using deployed smart contracts. ### Submit request The entire submission process requires only five key steps: ```solidity title="scrip/fdcExample/Base.s.sol" function submitAttestationRequest( bytes memory abiEncodedRequest ) internal { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( abiEncodedRequest ); console.log("request fee: %s\n", requestFee); vm.stopBroadcast(); vm.startBroadcast(deployerPrivateKey); IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); fdcHub.requestAttestation{value: requestFee}(abiEncodedRequest); vm.stopBroadcast(); } ``` ### Step-by-Step Breakdown 1. Load Private Key The private key is read from the `.env` file using Foundry's `envUint` function: ```solidity uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); ``` 2. Obtain Request Fee We retrieve the required requestFee from the `FdcRequestFeeConfigurations` contract: ```solidity IFdcRequestFeeConfigurations fdcRequestFeeConfigurations = ContractRegistry .getFdcRequestFeeConfigurations(); uint256 requestFee = fdcRequestFeeConfigurations.getRequestFee( abiEncodedRequest ); ``` This is done in a separate broadcast to ensure `requestFee` is available before submitting the request. 3. Access `FdcHub` Contract Using the `ContractRegistry` library (from `flare-periphery`), we fetch the `FdcHub` contract: ```solidity IFdcHub fdcHub = ContractRegistry.getFdcHub(); console.log("fcdHub address:"); console.log(address(fdcHub)); console.log("\n"); ``` 4. Submit the Attestation Request We send the attestation request with the required fee: ```solidity fdcHub.requestAttestation{value: requestFee}(abiEncodedRequest); ``` 5. Calculate the Voting Round Number To determine the voting round in which the attestation request is processed, we query the `FlareSystemsManager` contract: ```solidity // Calculating roundId IFlareSystemsManager flareSystemsManager = ContractRegistry .getFlareSystemsManager(); uint32 roundId = flareSystemsManager.getCurrentVotingEpochId(); console.log("roundId: %s\n", Strings.toString(roundId)); ``` This can be done within the existing broadcast or in a new one (as done in the demo repository for better code organization). ## Wait for response We wait for the round to finalize. This takes no more than 180 seconds. You can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. To learn more about how the FDC protocol works, check [here](/fdc/overview). ## Prepare proof request We prepare the proof request in a similar manner as in the step Prepare the request, by string concatenation. We import two new variables from the `.env` file; the URL of a verifier server and the corresponding API key. ```solidity title="scrip/fdcExample/JsonApi.s.sol" string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); ``` Also, by repeatedly using the Foundry shortcode `vm.readLine`, we read the data, saved to a file in the previous step, to variables. ```solidity title="scrip/fdcExample/JsonApi.s.sol" string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); ``` The code is as follows. ```solidity title="scrip/fdcExample/JsonApi.s.sol" contract RetrieveDataAndProof is Script { using Surl for *; function run() external { string memory daLayerUrl = vm.envString("COSTON2_DA_LAYER_URL"); string memory apiKey = vm.envString("X_API_KEY"); string memory requestBytes = vm.readLine( string.concat( dirPath, attestationTypeName, "_abiEncodedRequest", ".txt" ) ); string memory votingRoundId = vm.readLine( string.concat( dirPath, attestationTypeName, "_votingRoundId", ".txt" ) ); console.log("votingRoundId: %s\n", votingRoundId); console.log("requestBytes: %s\n", requestBytes); string[] memory headers = Base.prepareHeaders(apiKey); string memory body = string.concat( '{"votingRoundId":', votingRoundId, ',"requestBytes":"', requestBytes, '"}' ); console.log("body: %s\n", body); console.log( "headers: %s", string.concat("{", headers[0], ", ", headers[1]), "}\n" ); ... } } ``` ## Post proof request to DA Layer We post the proof request to a chosen DA Layer provider server also with the same code as we did in the previous step. ```solidity title="scrip/fdcExample/JsonApi.s.sol" string memory url = string.concat( daLayerUrl, // "api/v0/fdc/get-proof-round-id-bytes" "api/v1/fdc/proof-by-request-round-raw" ); console.log("url: %s\n", url); (, bytes memory data) = Base.postAttestationRequest(url, headers, body); ``` Parsing the returned data requires the definition of an auxiliary `struct`. {/* TODO rename after renaming */} ```solidity title="scrip/fdcExample/Base.s.sol" struct ParsableProof { bytes32 attestationType; bytes32[] proofs; bytes responseHex; } ``` The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x4164647265737356616c69646974790000000000000000000000000000000000`. The array `proofs` holds the Merkle proofs of our attestation request. Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IJsonApi.Response` struct. We retrieve this data as follows. ```solidity title="scrip/fdcExample/JsonApi.s.sol" bytes memory dataJson = parseData(data); ParsableProof memory proof = abi.decode(dataJson, (ParsableProof)); IJsonApi.Response memory proofResponse = abi.decode( proof.responseHex, (IJsonApi.Response) ); ```
An example complete proof response and decoded `IEVMTransaction.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 0000000000000000000000000000000000000000000000000000000000000020 494a736f6e417069000000000000000000000000000000000000000000000000 5745423200000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6c41 0000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c0 00000000000000000000000000000000000000000000000000000000000003a0 0000000000000000000000000000000000000000000000000000000000000060 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000140 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e6465762f6170692f70656f706c652f332f00 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d325d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000100 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000060 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000006 0000000000000000000000000000000000000000000000000000000000000003 0000000000000000000000000000000000000000000000000000000000000005 52322d4432000000000000000000000000000000000000000000000000000000', attestation_type: '0x494a736f6e417069000000000000000000000000000000000000000000000000', proof: [ '0xab2384609341b65b4686cf9accd981f8c7f58e47aa41bc49ec60f655c8d99840', '0xc30b0590a4ea59adc7aa5486a9ece81bdcc756ac4d22e09dbe171bf8b50e53b5', '0x4a5fc6d814dd3c52e199396de4d48f7c18274bbf72f9065c1e68306f4fd22c34' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IEVMTransaction.Response` struct is: ```shell [ attestationType: '0x494a736f6e417069000000000000000000000000000000000000000000000000', sourceId: '0x5745423200000000000000000000000000000000000000000000000000000000', votingRound: '945217', lowestUsedTimestamp: '0', requestBody: [ 'https://swapi.dev/api/people/3/', '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-2] | tonumber)}', '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}', url: 'https://swapi.dev/api/people/3/', postprocessJq: '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-2] | tonumber)}', abi_signature: '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}' ], responseBody: [ '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000', abi_encoded_data: '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000' ] ] ```
## Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. To verify address validity, we first format our data using the `IEVMTransaction.Proof` struct, which contains both the Merkle proof and the response data. ```solidity title="scrip/fdcExample/JsonApi.s.sol" IJsonApi.Proof memory _proof = IJsonApi.Proof( proof.proofs, proofResponse ); ``` We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If the proof is valid, the function `verifyJsonApi` will return `true`, otherwise `false`. As before, we wrap the whole thing into a broadcast environment, using the `PRIVATE_KEY` variable from our `.env` file. ```solidity title="scrip/fdcExample/JsonApi.s.sol" uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); bool isValid = ContractRegistry .getFdcVerification() .verifyJsonApi(proof); console.log("proof is valid: %s\n", StringsBase.toString(isValid)); vm.stopBroadcast(); ``` In actuality, we will only verify the proof within a deployed contract, which we will define in the next step. What we will do here instead is, we will save the proof to a file so that it can be later loaded into a variable. The code that does this is as follows. ```solidity title="scrip/fdcExample/EVMTransaction.s.sol" Base.writeToFile( dirPath, string.concat(attestationTypeName, "_proof"), StringsBase.toHexString(abi.encode(_proof)), true ); ``` ## Use the data {/* TODO */} We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive character data from the [Star Wars API](https://swapi.dev/), and store it in a `StarWarsCharacter` struct. It will do so only if the proof is valid. ```solidity title="src/fdcExample/JsonApi.sol" struct StarWarsCharacter { string name; uint256 numberOfMovies; uint256 apiUid; uint256 bmi; } ``` We will also need a `DataTransportObject` struct, that will allow us to decode the data. ```solidity title="src/fdcExample/JsonApi.sol" struct DataTransportObject { string name; uint256 height; uint256 mass; uint256 numberOfMovies; uint256 apiUid; } ``` First, we define an interface that the contract will inherit from. We do so, so that we may contact the contract later through a script. ```solidity title="src/fdcExample/JsonApi.sol" interface IStarWarsCharacterList { function addCharacter(IJsonApi.Proof calldata data) external; function getAllCharacters() external view returns (StarWarsCharacter[] memory); } ``` The interface exposes the two functions that a user might call, `addCharacter` and `getAllCharacters`. We now define the contract as follows. ```solidity title="src/fdcExample/JsonApi.sol" contract StarWarsCharacterList { mapping(uint256 => StarWarsCharacter) public characters; uint256[] public characterIds; function isJsonApiProofValid( IJsonApi.Proof calldata _proof ) private view returns (bool) { // Inline the check for now until we have an official contract deployed return ContractRegistry.auxiliaryGetIJsonApiVerification().verifyJsonApi( _proof ); } function addCharacter(IJsonApi.Proof calldata data) public { require(isJsonApiProofValid(data), "Invalid proof"); DataTransportObject memory dto = abi.decode( data.data.responseBody.abi_encoded_data, (DataTransportObject) ); require(characters[dto.apiUid].apiUid == 0, "Character already exists"); StarWarsCharacter memory character = StarWarsCharacter({ name: dto.name, numberOfMovies: dto.numberOfMovies, apiUid: dto.apiUid, bmi: (dto.mass * 100 * 100) / (dto.height * dto.height) }); characters[dto.apiUid] = character; characterIds.push(dto.apiUid); } function getAllCharacters() public view returns (StarWarsCharacter[] memory) { StarWarsCharacter[] memory result = new StarWarsCharacter[]( characterIds.length ); for (uint256 i = 0; i < characterIds.length; i++) { result[i] = characters[characterIds[i]]; } return result; } } ``` We deploy the contract through a simple script. The script creates a new `StarWarsCharacterList` contract and writes its address to a file (`data/JsonApi_listenerAddress.txt`). ```solidity title="scrip/fdcExample/DeployContract.s.sol" contract DeployContract is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); StarWarsCharacterList characterList = new StarWarsCharacterList(); address _address = address(characterList); vm.stopBroadcast(); Base.writeToFile( dirPath, string.concat(attestationTypeName, "_address"), StringsBase.toHexString(abi.encodePacked(_address)), true ); } } ``` We deploy the contract with the following console command. ```bash forge script script/fdcExample/JsonApi.s.sol:DeployContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --verify --ffi ``` Lastly, we define a script that interacts with the above contract. It first reads the ABI-encoded proof data, and the contract address, from files. Then, it connects to the above contract at the saved address (this is why we require the interface). With that, it can call the `registerJsonApi` method of the contract. ```solidity title="script/fdcExample/JsonApi.s.sol" contract InteractWithContract is Script { function run() external { string memory addressString = vm.readLine( string.concat(dirPath, attestationTypeName, "_address", ".txt") ); address _address = vm.parseAddress(addressString); string memory proofString = vm.readLine( string.concat(dirPath, attestationTypeName, "_proof", ".txt") ); bytes memory proofBytes = vm.parseBytes(proofString); IJsonApi.Proof memory proof = abi.decode(proofBytes, (IJsonApi.Proof)); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); IStarWarsCharacterList characterList = IStarWarsCharacterList(_address); characterList.addCharacter(proof); vm.stopBroadcast(); } } ``` We run this script with the console command: ```bash forge script script/fdcExample/JsonApi.s.sol:InteractWithContract --private-key $PRIVATE_KEY --rpc-url $COSTON2_RPC_URL --etherscan-api-key $FLARE_RPC_API_KEY --broadcast --ffi ``` --- ## AddressValidity(Hardhat) The [AddressValidity](/fdc/attestation-types/address-validity) attestation type validates whether a string represents a valid address on supported blockchain networks (`BTC`, `DOGE`, and `XRP`). This validation ensures addresses meet chain-specific formatting and checksum requirements before they're used in transactions or smart contracts. The full specification is available on the official [specification repo](/fdc/attestation-types/address-validity). The primary contract interface for this attestation type is [`IAddressValidity`](/fdc/reference/IFdcHub). Let's walk through validating a Bitcoin testnet address using the FDC protocol. We will use the address `mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs` as an example throughout this guide. You can swap this with any valid testnet address from the supported chains. You can follow this tutorial with any other valid address - just make sure it is a valid testnet address. This validation process works identically for `BTC`, `DOGE`, and `XRP` addresses, with only minor chain-specific parameter adjustments which we'll highlight throughout the guide. In this guide, we will follow the steps outlined in the [FDC overview](/fdc/overview). We will define a `scripts/fdcExample/AddressValidity.ts` file that will encapsulate the whole process. In the [Flare Hardhat Starter Kit](https://github.com/flare-foundation/flare-hardhat-starter), the helper functions used by this guide (`prepareAttestationRequestBase`, `submitAttestationRequest`, `retrieveDataAndProofBase`, `getFdcHub`, `getFdcRequestFeeConfigurations`, `getFlareSystemsManager`, `getRelay`, `getFdcVerification`) live in `scripts/utils/fdc.ts`, `scripts/utils/getters.ts`, and `scripts/utils/core.ts`. The snippets below use the same names for readability. ```typescript title="scripts/fdcExample/AddressValidity.ts" prepareAttestationRequestBase, submitAttestationRequest, retrieveDataAndProofBase, } from "../utils/fdc"; const AddressRegistry = artifacts.require("AddressRegistry"); const { VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL } = process.env; ... async function main() { const data = await prepareAttestationRequest(addressStr); console.log("Data:", data, "\n"); const abiEncodedRequest = data.abiEncodedRequest; const roundId = await submitAttestationRequest(abiEncodedRequest); const proof = await retrieveDataAndProof(abiEncodedRequest, roundId); const addressRegistry: AddressRegistryInstance = await deployAndVerifyContract(); await interactWithContract(addressRegistry, proof); } main().then((data) => { process.exit(0); }); ``` The function names mostly mirror the steps described in the [FDC guide](/fdc/overview). ## Prepare request In this guide we will demonstrate how to prepare an attestation request through a verifier server. At the end of the section we will provide a breakdown of the abi encoded request; thus we will demonstrate how it can be constructed manually. To use the verifier server, we send a request to its `prepareRequest` endpoint. A JSON request to the verifier follows the same structure for all attestation types, with field values varying between types. ### Required Fields - `attestationType`: UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId`: UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody`: Specific to each attestation type. For `AddressValidity`, `requestBody` contains a single field: - `addressStr`: The address to verify. ### Reference Documentation - [AddressValidity Specification](/fdc/attestation-types/address-validity) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest) - API available for [DOGE](https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest) and [XRP](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest). ### Example Values - `addressStr`: `mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs`. - `attestationType`: UTF8 hex encoding of `AddressValidity`, zero-padded to 32 bytes. - `sourceId`: UTF8 hex encoding of `testBTC`, zero-padded to 32 bytes. - `"test"` prefix denotes Bitcoin testnet. - Supports deployment on Flare testchains (`Coston` or `Coston2`). - Replace `testBTC` with `testDOGE` or `testXRP` for other chains. - `urlTypeBase`: string `btc_testnet4` - Replace `btc_testnet4` with `doge` or `xrp` for other chains. ```typescript title="scripts/fdcExample/AddressValidity.ts" // Request data const addressStr = "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs"; // Configuration constants const attestationTypeBase = "AddressValidity"; const sourceIdBase = "testBTC"; const verifierUrlBase = VERIFIER_URL_TESTNET; const urlTypeBase = "btc_testnet4"; ``` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHex`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-hardhat-starter/blob/master/scripts/fdcExample/Base.ts) within the [example repository](https://github.com/flare-foundation/flare-hardhat-starter/), but they can also be defined locally in your contract or script. ```typescript title="scripts/utils/fdc.ts" function toHex(data: string) { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return result.padEnd(64, "0"); } ``` ```typescript title="scripts/utils/fdc.ts" function toUtf8HexString(data: string) { return "0x" + toHex(data); } ``` Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/AddressValidity/BTCAddressValidityVerifierController_prepareRequest) to check what the response is. The process of posting a request to a verifier server is identical for all attestation types. It differs only in values used. For that reason we define a base function that the `prepareAttestationRequest` function will call. The `prepareAttestationRequestBase` function formulates a request for the verifier server, and posts it to the given URL. ```typescript title="scripts/utils/fdc.ts" async function prepareAttestationRequestBase( url: string, apiKey: string, attestationTypeBase: string, sourceIdBase: string, requestBody: any, ) { console.log("Url:", url, "\n"); const attestationType = toUtf8HexString(attestationTypeBase); const sourceId = toUtf8HexString(sourceIdBase); const request = { attestationType: attestationType, sourceId: sourceId, requestBody: requestBody, }; console.log("Prepared request:\n", request, "\n"); const response = await fetch(url, { method: "POST", headers: { "X-API-KEY": apiKey, "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } console.log("Response status is OK\n"); return await response.json(); } ``` In the example repository, it is once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/scripts/fdcExample/Base.s.sol) library file. We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network` the path `/verifier/btc_testnet4/AddressValidity/prepareRequest`. If we were using another source, we would replace `btc_testnet4` with `doge` or `xrp` accordingly (we would also have to replace `testBTC` with `testDOGE` or `testXRP`). Thus, the function that prepares the verifier request looks like: ```typescript title="scripts/fdcExample/AddressValidity.ts" async function prepareAttestationRequest(addressStr: string) { const requestBody = { addressStr: addressStr, }; const url = `${verifierUrlBase}/verifier/${urlTypeBase}/AddressValidity/prepareRequest`; const apiKey = VERIFIER_API_KEY_TESTNET ?? ""; return await prepareAttestationRequestBase( url, apiKey, attestationTypeBase, sourceIdBase, requestBody, ); } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 4164647265737356616c69646974790000000000000000000000000000000000 7465737442544300000000000000000000000000000000000000000000000000 7d2ef938d4ffd2392f588bf46563e07ab885b15fead91c1bb99b16f465b71a68 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("AddressValidity")` - **Second line:** `toUtf8HexString("testBTC")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `AddressValidity.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. We will submit the validated request to the blockchain using deployed official Flare smart contracts. To streamline the process of accessing these, the [Flare smart contracts periphery package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) is shipped with the `ContractRegistry` library. The `ContractRegistry` library is a Solidity helper that resolves the addresses of Flare's official contracts dynamically through the `FlareContractRegistry` deployed at the canonical address `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` on all Flare-family networks. To submit and verify FDC attestations we need addresses for: - `FdcHub`: for posting the request to - `FdcRequestFeeConfigurations`: calculates the fee of the request - `FlareSystemsManager`: for calculating the round ID - `Relay`: confirms the round has finalized - `FdcVerification`: exposes the FDC protocol id and verification entry points The starter resolves these from TypeScript via small wrappers in `scripts/utils/getters.ts` that call `IFlareContractRegistry.getContractAddressByName(...)` and return a typechain instance. The relevant helpers are `getFdcHub`, `getFdcRequestFeeConfigurations`, `getFlareSystemsManager`, `getRelay`, and `getFdcVerification`. We import them from that file in the snippets below. To submit the attestation request, we first access the deployed `FdcHub` contract. We determine the fee for our attestation type, and then request the attestation of the FDC, paying the required fee. Lastly, we calculate the voting round Id from the transaction that carried the attestation request; we will need it to query the data and proof. ```typescript title="scripts/utils/fdc.ts" async function submitAttestationRequest(abiEncodedRequest: string) { const fdcHub = await getFdcHub(); const requestFee = await getFdcRequestFee(abiEncodedRequest); const transaction = await fdcHub.requestAttestation(abiEncodedRequest, { value: requestFee, }); console.log("Submitted request:", transaction.tx, "\n"); const roundId = await calculateRoundId(transaction); console.log( `Check round progress at: https://${hre.network.name}-systems-explorer.flare.rocks/voting-round/${roundId}?tab=fdc\n`, ); return roundId; } ``` {/* TODO */} ```typescript title="scripts/utils/getters.ts" async function getFdcHub() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcHubAddress: string = await registry.getContractAddressByName("FdcHub"); return await FdcHub.at(fdcHubAddress); } ``` The request fee is obtained from the `fdcRequestFeeConfigurations` contract. We once again connect to the `fdcRequestFeeConfigurations` contract through the `IFlareContractRegistry` library. ```typescript title="scripts/utils/fdc.ts" async function getFdcRequestFee(abiEncodedRequest: string) { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcRequestFeeConfigurationsAddress: string = await registry.getContractAddressByName("FdcRequestFeeConfigurations"); const fdcRequestFeeConfigurations: IFdcRequestFeeConfigurationsInstance = await FdcRequestFeeConfigurations.at(fdcRequestFeeConfigurationsAddress); return await fdcRequestFeeConfigurations.getRequestFee(abiEncodedRequest); } ``` The round ID is calculate from the timestamp of the block, containing the transaction requesting attestation. We first subtract from the block timestamp the timestamp of the first voting epoch. Then, we divide the number by the duration of the voting epoch (90 seconds). Instead of hard-coding them, we retrieve these values from another official Flare contract, the `flareSystemsManager`. ```typescript title="scripts/utils/fdc.ts" async function calculateRoundId(transaction: any) { const blockNumber = transaction.receipt.blockNumber; const block = await ethers.provider.getBlock(blockNumber); const blockTimestamp = BigInt(block!.timestamp); const flareSystemsManager: IFlareSystemsManagerInstance = await getFlareSystemsManager(); const firsVotingRoundStartTs = BigInt( await flareSystemsManager.firstVotingRoundStartTs(), ); const votingEpochDurationSeconds = BigInt( await flareSystemsManager.votingEpochDurationSeconds(), ); console.log("Block timestamp:", blockTimestamp, "\n"); console.log("First voting round start ts:", firsVotingRoundStartTs, "\n"); console.log( "Voting epoch duration seconds:", votingEpochDurationSeconds, "\n", ); const roundId = Number( (blockTimestamp - firsVotingRoundStartTs) / votingEpochDurationSeconds, ); console.log("Calculated round id:", roundId, "\n"); console.log( "Received round id:", Number(await flareSystemsManager.getCurrentVotingEpochId()), "\n", ); return roundId; } ``` We obtain the `flareSystemsManager` contract through the `IFlareContractRegistry`. ```typescript title="scripts/utils/getters.ts" async function getFlareSystemsManager() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const flareSystemsManagerAddress: string = await registry.getContractAddressByName("FlareSystemsManager"); return await FlareSystemsManager.at(flareSystemsManagerAddress); } ``` ## Retrieve data and proof To retrieve the data and proof, we must first wait for the voting round in which the attestation request was submitted to finalize; this takes no more than 180 seconds, but is on average much less. After the round has been finalized, we post a request to a DA Layer provider. We can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. If you want to learn more about how the FDC protocol works, check [here](/fdc/overview). Because the process includes waiting for the voting round to finalize, we prepare a `sleep` function. The function pauses the execution of the script for a given number of milliseconds. ```typescript title="scripts/utils/fdc.ts" function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } ``` The only difference between the `retrieveDataAndProof` functions of all six attestation types is the URL of the DA Layer server. For that reason, we will define as separate `retrieveDataAndProofBase` function that will handle most of the logic. The function waits for the round to finalize - rechecking every 10 seconds if necessary. Then, it prepares a proof request, and posts it to the DA Layer server. Because it might take a few seconds for the server to generate the proof, the function ensures that the response actually contains a sufficient response, and retries otherwise. ```typescript title="scripts/fdcExample/AddressValidity.ts" async function retrieveDataAndProofBase( url: string, abiEncodedRequest: string, roundId: number, ) { console.log("Waiting for the round to finalize..."); // We check every 10 seconds if the round is finalized const relay: IRelayInstance = await getRelay(); const fdcVerification: IFdcVerificationInstance = await getFdcVerification(); const protocolId = await fdcVerification.fdcProtocolId(); while (!(await relay.isFinalized(protocolId, roundId))) { await sleep(10000); } console.log("Round finalized!\n"); const request = { votingRoundId: roundId, requestBytes: abiEncodedRequest, }; console.log("Prepared request:\n", request, "\n"); await sleep(10000); var proof = await postRequestToDALayer(url, request, true); console.log("Waiting for the DA Layer to generate the proof..."); while (proof.response_hex == undefined) { await sleep(5000); proof = await postRequestToDALayer(url, request, false); } console.log("Proof generated!\n"); console.log("Proof:", proof, "\n"); return proof; } ``` We access the Flare's official `Relay` contract with a helper function. ```typescript title="scripts/utils/getters.ts" async function getRelay() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const relayAddress: string = await registry.getContractAddressByName("Relay"); return await IRelay.at(relayAddress); } async function getFdcVerification() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcVerificationAddress: string = await registry.getContractAddressByName("FdcVerification"); return await IFdcVerification.at(fdcVerificationAddress); } ``` The following function posts a proof request to the DA Layer. ```typescript title="scripts/utils/fdc.ts" async function postRequestToDALayer( url: string, request: any, watchStatus: boolean = false, ) { const response = await fetch(url, { method: "POST", headers: { // "X-API-KEY": "", "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (watchStatus && response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } else if (watchStatus) { console.log("Response status is OK\n"); } return await response.json(); } ``` The main prepare the URL of the DA Layer's `proof-by-request-raw` endpoint. We contact this specific endpoint, because it return the abi encoded `IAddressValidity.Response` struct, and is thus unambiguous. ```typescript title="scripts/fdcExample/AddressValidity.ts" async function retrieveDataAndProof( abiEncodedRequest: string, roundId: number, ) { const url = `${COSTON2_DA_LAYER_URL}/api/v1/fdc/proof-by-request-round-raw`; console.log("Url:", url, "\n"); return await retrieveDataAndProofBase(url, abiEncodedRequest, roundId); } ``` The response the DA Layer server returns has the following fields: - The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x4164647265737356616c69646974790000000000000000000000000000000000`. - The array `proofs` holds the Merkle proofs of our attestation request. - Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IAddressValidity.Response` struct. We can ascertain the form of the proof request, as well as examine the response in advance, trough the [interactive documentation](https://ctn2-data-availability.flare.network/api-doc#/) of the DA Layer server.
An example complete proof response and decoded `IAddressValidity.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: "0x 0000000000000000000000000000000000000000000000000000000000000020 4164647265737356616c69646974790000000000000000000000000000000000 7465737442544300000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6bda 000000000000000000000000000000000000000000000000ffffffffffffffff 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000140 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000060 6810e152510fe893f9cc8954c4dfaecd5c2be00e2732d6fe3e25922f30c5a3c5 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000", attestation_type: "0x4164647265737356616c69646974790000000000000000000000000000000000", proof: [ "0x275dc338dd4e6a0a8749caa098c6749e0e77e22ba9db264f334b5dfb79aa6321", "0x084e002bbe12f4a163d82ddd17861d1d3131c816fe3b998d575d134043a6c8f1", "0xc30304c7d430e3d0f83d05017035f13ca19dec2799917745967f4c48685eab49", "0x4d622137c9e7c9a1fa3a5d2942a183a8e926ba8659fe606495ea994acbb6ec0f" ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IAddressValidity.Response` struct is: ```shell [ attestationType: "0x4164647265737356616c69646974790000000000000000000000000000000000", sourceId: "0x7465737442544300000000000000000000000000000000000000000000000000", votingRound: "945114", lowestUsedTimestamp: "18446744073709551615", requestBody: [ "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs", addressStr: "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs" ], responseBody: [ true, "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs", "0x6810e152510fe893f9cc8954c4dfaecd5c2be00e2732d6fe3e25922f30c5a3c5", isValid: true, standardAddress: "mg9P9f4wr9w7c1sgFeiTC5oMLYXCc2c7hs", standardAddressHash: "0x6810e152510fe893f9cc8954c4dfaecd5c2be00e2732d6fe3e25922f30c5a3c5" ] ] ```
## Use the data We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive an address and proof, and decide if the address is valid. If the address is valid, the contract will add it to an array of valid addresses. Otherwise, it will raise an error. The code for this contract is as follows. ```solidity title="contracts/fdcExample/AddressValidity.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; interface IAddressRegistry { function registerAddress(IAddressValidity.Proof memory _transaction) external; } contract AddressRegistry is IAddressRegistry { string[] public verifiedAddresses; function isAddressValidityProofValid(IAddressValidity.Proof memory transaction) public view returns (bool) { // Use the library to get the verifier contract and verify that this transaction was proved by the FDC IFdcVerification fdc = ContractRegistry.getFdcVerification(); return fdc.verifyAddressValidity(transaction); } function registerAddress(IAddressValidity.Proof memory _transaction) public { // 1. FDC Logic // Check that this AddressValidity has indeed been confirmed by the FDC require(isAddressValidityProofValid(_transaction), "Invalid transaction proof"); // 2. Business logic string memory provedAddress = _transaction.data.requestBody.addressStr; verifiedAddresses.push(provedAddress); } } ``` ### Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If we proof is valid, the function `verifyAddressValidity` will return `true`, otherwise `false`. We deploy and verify this contract with the `deployAndVerifyContract` function in the `scripts/fdcExample/AddressValidity.ts` file. ```typescript title="scripts/fdcExample/AddressValidity.ts" async function deployAndVerifyContract() { const args: any[] = []; const addressRegistry: AddressRegistryInstance = await AddressRegistry.new( ...args, ); try { await run("verify:verify", { address: addressRegistry.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log("AddressRegistry deployed to", addressRegistry.address, "\n"); return addressRegistry; } ``` ## Interact with contract We define an additional function that allows us to interact with the just deployed contract. The `interactWithContract` function also takes the proof retrieved in the previous step as an argument. It abi decodes the `response_hex` value to an `IAddressValidity.Response` struct. From that and the array of proofs, it constructs an `IAddressValidity.Proof` object, on which it call the `registerAddress` function of the `AddressRegistry` contract deployed above. The contract verifies the address, and the script prints it to the console. ```typescript title="scripts/fdcExample/AddressValidity.ts" async function interactWithContract( addressRegistry: AddressRegistryInstance, proof: any, ) { console.log("Proof hex:", proof.response_hex, "\n"); // A piece of black magic that allows us to read the response type from an artifact const IAddressValidityVerification = await artifacts.require( "IAddressValidityVerification", ); const responseType = IAddressValidityVerification._json.abi[0].inputs[0].components[1]; console.log("Response type:", responseType, "\n"); const decodedResponse = web3.eth.abi.decodeParameter( responseType, proof.response_hex, ); console.log("Decoded proof:", decodedResponse, "\n"); const transaction = await addressRegistry.registerAddress({ merkleProof: proof.proof, data: decodedResponse, }); console.log("Transaction:", transaction.tx, "\n"); console.log( "Verified address:", await addressRegistry.verifiedAddresses(0), "\n", ); } ``` We can run the whole script by calling the following console command. ```bash yarn hardhat run scripts/fdcExample/AddressValidity.ts --network coston2 ``` --- ## EVMTransaction(Hardhat) The [`EVMTransaction`](/fdc/attestation-types/evm-transaction) attestation type enables data collection about a transaction on an EVM chain. The currently supported chain are: `ETH`, `FLR`, and `SGB`. You can learn more about it in the official [specification repo](/fdc/attestation-types/evm-transaction). We will now demonstrate how the FDC protocol can be used to collect the data of a given Ethereum transaction. The transaction we will be observing has the hash `0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c`; this is an arbitrary transaction that we acquired from the Sepolia Ethereum testnet [explorer](https://sepolia.etherscan.io/). The same procedure works for all supported sources, `ETH`, `FLR`, and `SGB`. The source then requires only a slight modification; we will remind you of that when it comes up in the guide. In this guide, we will be following the steps outlined in the [FDC Overview](/fdc/overview). We will define a `scripts/fdcExample/EVMTransaction.ts` file that will encapsulate the whole process. The shared helpers used below live in the starter under `scripts/utils/fdc.ts` and `scripts/utils/getters.ts`. ```typescript title="scripts/fdcExample/EVMTransaction.ts" prepareAttestationRequestBase, submitAttestationRequest, retrieveDataAndProofBase, } from "../utils/fdc"; const EVMTransaction = artifacts.require("TransferEventListener"); const { VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL } = process.env; ... async function main() { const data = await prepareAttestationRequest(transactionHash); console.log("Data:", data, "\n"); const abiEncodedRequest = data.abiEncodedRequest; const roundId = await submitAttestationRequest(abiEncodedRequest); const proof = await retrieveDataAndProof(abiEncodedRequest, roundId); const eventListener: TransferEventListenerInstance = await deployAndVerifyContract(); await interactWithContract(eventListener, proof); } main().then((data) => { process.exit(0); }); ``` The function names mostly mirror the steps described in the [FDC guide](/fdc/overview). ## Prepare request In this guide we will demonstrate how to prepare an attestation request through a verifier server. At the end of the section we will provide a breakdown of the abi encoded request; thus we will demonstrate how it can be constructed manually. To use the verifier server, we send a request to its `prepareRequest` endpoint. A JSON request to the verifier follows the same structure for all attestation types, with field values varying between types. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `EVMTransaction`, `requestBody` is a JSON containing the fields: - `transactionHash`: hash (address) of the observed transaction - `requiredConfirmations`: the depth of the block containing the transaction at which it is considered confirmed, i.e. when the transaction itself is considered confirmed; as `uint16` - `provideInput`: a `bool` determining whether the `input` field is included in the response - `listEvents`: a `bool` determining whether the `events` field is included in the response - `logIndices`: an `uint32` array of indices of the events to be included in the response; if `listEvents` is set to false `false` and this field is not `[]`, the attestation request will fail ### Reference Documentation - [EVMTransaction Specification](/fdc/attestation-types/evm-transaction) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/api-doc#/) ### Example Values - `transactionHash`: `0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c` - `requiredConfirmations`: `1` - `provideInput`: `true` - `listEvents`: `true` - `logIndices`: `[]` - `urlTypeBase`: string `eth` - Replace `eth` with `sgb` or `flr` for other chains. ```typescript title="scripts/fdcExample/EVMTransaction.ts" // Request data const transactionHash = "0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c"; // Configuration constants const attestationTypeBase = "EVMTransaction"; const sourceIdBase = "testETH"; const verifierUrlBase = VERIFIER_URL_TESTNET; const urlTypeBase = "eth"; ``` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHex`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-hardhat-starter/blob/master/scripts/fdcExample/Base.ts) within the [example repository](https://github.com/flare-foundation/flare-hardhat-starter/), but they can also be defined locally in your contract or script. ```typescript title="scripts/utils/fdc.ts" function toHex(data: string) { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return result.padEnd(64, "0"); } ``` ```typescript title="scripts/utils/fdc.ts" function toUtf8HexString(data: string) { return "0x" + toHex(data); } ``` Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/api-doc/) to check what the response will be. The process of posting a request to a verifier server is identical for all attestation types. It differs only in values used. For that reason we define a base function that the `prepareAttestationRequest` function will call. The `prepareAttestationRequestBase` function formulates a request for the verifier server, and posts it to the given URL. ```typescript title="scripts/utils/fdc.ts" async function prepareAttestationRequestBase( url: string, apiKey: string, attestationTypeBase: string, sourceIdBase: string, requestBody: any, ) { console.log("Url:", url, "\n"); const attestationType = toUtf8HexString(attestationTypeBase); const sourceId = toUtf8HexString(sourceIdBase); const request = { attestationType: attestationType, sourceId: sourceId, requestBody: requestBody, }; console.log("Prepared request:\n", request, "\n"); const response = await fetch(url, { method: "POST", headers: { "X-API-KEY": apiKey, "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } console.log("Response status is OK\n"); return await response.json(); } ``` In the example repository, it is once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/scripts/fdcExample/Base.s.sol) library file. We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/` the path `/verifier/eth/EVMTransaction/prepareRequest`. If we were using another source, we would replace the string `eth` with `sgb` or `flr` accordingly (we would also have to replace `testETH` with `testSGB` or `testFLR`). Thus, the function that prepares the verifier request looks like: ```typescript title="scripts/fdcExample/EVMTransaction.ts" async function prepareAttestationRequest(transactionHash: string) { const requiredConfirmations = "1"; const provideInput = true; const listEvents = true; const logIndices: string[] = []; const requestBody = { transactionHash: transactionHash, requiredConfirmations: requiredConfirmations, provideInput: provideInput, listEvents: listEvents, logIndices: logIndices, }; const url = `${verifierUrlBase}/verifier/${urlTypeBase}/EVMTransaction/prepareRequest`; const apiKey = VERIFIER_API_KEY_TESTNET!; return await prepareAttestationRequestBase( url, apiKey, attestationTypeBase, sourceIdBase, requestBody, ); } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 45564d5472616e73616374696f6e000000000000000000000000000000000000 7465737445544800000000000000000000000000000000000000000000000000 9d410778cc0b2b8f1b8eaa79cbd0eed5d3be7514dea070e2041ad00a4c6e88f8 0000000000000000000000000000000000000000000000000000000000000020 4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("EVMTransaction")` - **Second line:** `toUtf8HexString("testETH")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `EVMTransaction.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. We will submit the validated request to the blockchain using deployed official Flare smart contracts. To streamline the process of accessing these, the [Flare smart contracts periphery package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) is shipped with the `ContractRegistry` library. The `IFlareContractRegistry` deployed at `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` on every Flare-family network resolves names to live contract addresses. We use it to discover: - `FdcHub`: for posting the request to - `FdcRequestFeeConfigurations`: calculates the fee of the request - `FlareSystemsManager`: for calculating the round ID - `Relay`: confirms the round has finalized - `FdcVerification`: exposes the FDC protocol id and verification entry points The starter resolves these from TypeScript via small wrappers in `scripts/utils/getters.ts` that call `IFlareContractRegistry.getContractAddressByName(...)` and return a typechain instance. To submit the attestation request, we first access the deployed `FdcHub` contract. We determine the fee for our attestation type, and then request the attestation of the FDC, paying the required fee. Lastly, we calculate the voting round Id from the transaction that carried the attestation request; we will need it to query the data and proof. ```typescript title="scripts/utils/fdc.ts" async function submitAttestationRequest(abiEncodedRequest: string) { const fdcHub = await getFdcHub(); const requestFee = await getFdcRequestFee(abiEncodedRequest); const transaction = await fdcHub.requestAttestation(abiEncodedRequest, { value: requestFee, }); console.log("Submitted request:", transaction.tx, "\n"); const roundId = await calculateRoundId(transaction); console.log( `Check round progress at: https://${hre.network.name}-systems-explorer.flare.rocks/voting-round/${roundId}?tab=fdc\n`, ); return roundId; } ``` {/* TODO */} ```typescript title="scripts/utils/getters.ts" async function getFdcHub() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcHubAddress: string = await registry.getContractAddressByName("FdcHub"); return await FdcHub.at(fdcHubAddress); } ``` The request fee is obtained from the `fdcRequestFeeConfigurations` contract. We once again resolve its address through the `IFlareContractRegistry`. ```typescript title="scripts/utils/fdc.ts" async function getFdcRequestFee(abiEncodedRequest: string) { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcRequestFeeConfigurationsAddress: string = await registry.getContractAddressByName("FdcRequestFeeConfigurations"); const fdcRequestFeeConfigurations: IFdcRequestFeeConfigurationsInstance = await FdcRequestFeeConfigurations.at(fdcRequestFeeConfigurationsAddress); return await fdcRequestFeeConfigurations.getRequestFee(abiEncodedRequest); } ``` The round ID is calculate from the timestamp of the block, containing the transaction requesting attestation. We first subtract from the block timestamp the timestamp of the first voting epoch. Then, we divide the number by the duration of the voting epoch (90 seconds). Instead of hard-coding them, we retrieve these values from another official Flare contract, the `flareSystemsManager`. ```typescript title="scripts/utils/fdc.ts" async function calculateRoundId(transaction: any) { const blockNumber = transaction.receipt.blockNumber; const block = await ethers.provider.getBlock(blockNumber); const blockTimestamp = BigInt(block!.timestamp); const flareSystemsManager: IFlareSystemsManagerInstance = await getFlareSystemsManager(); const firsVotingRoundStartTs = BigInt( await flareSystemsManager.firstVotingRoundStartTs(), ); const votingEpochDurationSeconds = BigInt( await flareSystemsManager.votingEpochDurationSeconds(), ); console.log("Block timestamp:", blockTimestamp, "\n"); console.log("First voting round start ts:", firsVotingRoundStartTs, "\n"); console.log( "Voting epoch duration seconds:", votingEpochDurationSeconds, "\n", ); const roundId = Number( (blockTimestamp - firsVotingRoundStartTs) / votingEpochDurationSeconds, ); console.log("Calculated round id:", roundId, "\n"); console.log( "Received round id:", Number(await flareSystemsManager.getCurrentVotingEpochId()), "\n", ); return roundId; } ``` We obtain the `flareSystemsManager` contract through the `IFlareContractRegistry`. ```typescript title="scripts/utils/getters.ts" async function getFlareSystemsManager() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const flareSystemsManagerAddress: string = await registry.getContractAddressByName("FlareSystemsManager"); return await FlareSystemsManager.at(flareSystemsManagerAddress); } ``` ## Retrieve data and proof To retrieve the data and proof, we must first wait for the voting round in which the attestation request was submitted to finalize; this takes no more than 180 seconds, but is on average much less. After the round has been finalized, we post a request to a DA Layer provider. We can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. If you want to learn more about how the FDC protocol works, check [here](/fdc/overview). Because the process includes waiting for the voting round to finalize, we prepare a `sleep` function. The function pauses the execution of the script for a given number of milliseconds. ```typescript title="scripts/utils/fdc.ts" function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } ``` The only difference between the `retrieveDataAndProof` functions of all six attestation types is the URL of the DA Layer server. For that reason, we will define as separate `retrieveDataAndProofBase` function that will handle most of the logic. The function waits for the round to finalize - rechecking every 10 seconds if necessary. Then, it prepares a proof request, and posts it to the DA Layer server. Because it might take a few seconds for the server to generate the proof, the function ensures that the response actually contains a sufficient response, and retries otherwise. ```typescript title="scripts/utils/fdc.ts" async function retrieveDataAndProofBase( url: string, abiEncodedRequest: string, roundId: number, ) { console.log("Waiting for the round to finalize..."); // We check every 10 seconds if the round is finalized const relay: IRelayInstance = await getRelay(); const fdcVerification: IFdcVerificationInstance = await getFdcVerification(); const protocolId = await fdcVerification.fdcProtocolId(); while (!(await relay.isFinalized(protocolId, roundId))) { await sleep(10000); } console.log("Round finalized!\n"); const request = { votingRoundId: roundId, requestBytes: abiEncodedRequest, }; console.log("Prepared request:\n", request, "\n"); await sleep(10000); var proof = await postRequestToDALayer(url, request, true); console.log("Waiting for the DA Layer to generate the proof..."); while (proof.response_hex == undefined) { await sleep(5000); proof = await postRequestToDALayer(url, request, false); } console.log("Proof generated!\n"); console.log("Proof:", proof, "\n"); return proof; } ``` We access the Flare's official `Relay` and `FdcVerification` contracts with helper functions. ```typescript title="scripts/utils/getters.ts" async function getRelay() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const relayAddress: string = await registry.getContractAddressByName("Relay"); return await IRelay.at(relayAddress); } async function getFdcVerification() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcVerificationAddress: string = await registry.getContractAddressByName("FdcVerification"); return await IFdcVerification.at(fdcVerificationAddress); } ``` The following function posts a proof request to the DA Layer. ```typescript title="scripts/utils/fdc.ts" async function postRequestToDALayer( url: string, request: any, watchStatus: boolean = false, ) { const response = await fetch(url, { method: "POST", headers: { // "X-API-KEY": "", "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (watchStatus && response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } else if (watchStatus) { console.log("Response status is OK\n"); } return await response.json(); } ``` The main prepare the URL of the DA Layer's `proof-by-request-raw` endpoint. We contact this specific endpoint, because it return the abi encoded `IEVMTransaction.Response` struct, and is thus unambiguous. ```typescript title="scripts/fdcExample/EVMTransaction.ts" async function retrieveDataAndProof( abiEncodedRequest: string, roundId: number, ) { const url = `${COSTON2_DA_LAYER_URL}/api/v1/fdc/proof-by-request-round-raw`; console.log("Url:", url, "\n"); return await retrieveDataAndProofBase(url, abiEncodedRequest, roundId); } ``` The response the DA Layer server returns has the following fields: - The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x45564d5472616e73616374696f6e000000000000000000000000000000000000`. - The array `proofs` holds the Merkle proofs of our attestation request. - Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IEVMTransaction.Response` struct. We can ascertain the form of the proof request, as well as examine the response in advance, trough the [interactive documentation](https://ctn2-data-availability.flare.network/api-doc#/) of the DA Layer server.
An example complete proof response and decoded `IEVMTransaction.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 0000000000000000000000000000000000000000000000000000000000000020 45564d5472616e73616374696f6e000000000000000000000000000000000000 7465737445544800000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6c2c 0000000000000000000000000000000000000000000000000000000067724b20 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000180 4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000070acc6 0000000000000000000000000000000000000000000000000000000067724b20 00000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a806 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 00000000000000000000000000000000000000000000000000d8b72d434c8000 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000520 00000000000000000000000000000000000000000000000000000000000003c5 3593564c00000000000000000000000000000000000000000000000000000000 0000006000000000000000000000000000000000000000000000000000000000 000000a000000000000000000000000000000000000000000000000000000000 6772521a00000000000000000000000000000000000000000000000000000000 000000040b000604000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000000400000000000000000000000000000000000000000000000000000000 0000008000000000000000000000000000000000000000000000000000000000 000000e000000000000000000000000000000000000000000000000000000000 0000020000000000000000000000000000000000000000000000000000000000 0000028000000000000000000000000000000000000000000000000000000000 0000004000000000000000000000000000000000000000000000000000000000 0000000200000000000000000000000000000000000000000000000000d8b72d 434c800000000000000000000000000000000000000000000000000000000000 0000010000000000000000000000000000000000000000000000000000000000 0000000200000000000000000000000000000000000000000000000000d8b72d 434c800000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 000000a000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41c7d4b196c b0c7b01d743fbc6116a902379c72380000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902 379c7238000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57a b7e35b9500000000000000000000000000000000000000000000000000000000 0000001900000000000000000000000000000000000000000000000000000000 000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902 379c723800000000000000000000000070ad32b82b4fe2821c798e628d936452 18e2a80600000000000000000000000000000000000000000000000000000000 ad2090e40c000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000006 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000200 0000000000000000000000000000000000000000000000000000000000000360 00000000000000000000000000000000000000000000000000000000000004c0 00000000000000000000000000000000000000000000000000000000000006a0 0000000000000000000000000000000000000000000000000000000000000800 000000000000000000000000000000000000000000000000000000000000003f 000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000100 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000d8b72d434c8000 0000000000000000000000000000000000000000000000000000000000000040 0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003289680dd4d6c10bb19b899729cda5eef58aeff1 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000ae6dcda8 0000000000000000000000000000000000000000000000000000000000000041 000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000003289680dd4d6c10bb19b899729cda5eef58aeff1 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000d8b72d434c8000 0000000000000000000000000000000000000000000000000000000000000042 0000000000000000000000003289680dd4d6c10bb19b899729cda5eef58aeff1 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 00000000000000000000000000000000000000000000000000000000000000a0 ffffffffffffffffffffffffffffffffffffffffffffffffffffffff51923258 00000000000000000000000000000000000000000000000000d8b72d434c8000 00000000000000000000000000000000000011d79ac448fce087b0605d7423c8 000000000000000000000000000000000000000000000000002231596d817570 000000000000000000000000000000000000000000000000000000000002925f 0000000000000000000000000000000000000000000000000000000000000043 0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57ab7e35b95 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000006fa26f 0000000000000000000000000000000000000000000000000000000000000044 0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000003 ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad 00000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a806 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000adfe2b39', attestation_type: '0x45564d5472616e73616374696f6e000000000000000000000000000000000000', proof: [ '0x9251c0e3047688af1305daf61f2b757527b731e7d1fad3c71d08734772fbebeb', '0xad5fdf0f8cb6bc42cdab5affb8f03a1fadaf1ef60875af76344b7ca3ab694c9b', '0xead06bac3be86604034e138784c86f0b14f2481c001e31e17d03c185488033dc' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IEVMTransaction.Response` struct is: ```shell [ attestationType: '0x45564d5472616e73616374696f6e000000000000000000000000000000000000', sourceId: '0x7465737445544800000000000000000000000000000000000000000000000000', votingRound: '945196', lowestUsedTimestamp: '1735543584', requestBody: [ '0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c', '1', true, true, [], transactionHash: '0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c', requiredConfirmations: '1', provideInput: true, listEvents: true, logIndices: [] ], responseBody: [ '7384262', '1735543584', '0x70Ad32B82B4FE2821C798e628d93645218E2A806', false, '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', '61000000000000000', '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772521a00000000000000000000000000000000000000000000000000000000000000040b000604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57ab7e35b95000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a80600000000000000000000000000000000000000000000000000000000ad2090e40c', '1', [ ... ], blockNumber: '7384262', timestamp: '1735543584', sourceAddress: '0x70Ad32B82B4FE2821C798e628d93645218E2A806', isDeployment: false, receivingAddress: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', value: '61000000000000000', input: '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772521a00000000000000000000000000000000000000000000000000000000000000040b000604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d8b72d434c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238000000000000000000000000e49acc3b16c097ec88dc9352ce4cd57ab7e35b95000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000000600000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000070ad32b82b4fe2821c798e628d93645218e2a80600000000000000000000000000000000000000000000000000000000ad2090e40c', status: '1', events: [ ... ] ] ] ```
## Use the data We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive data and proof of an Ethereum transaction, and store all token transfers contained into an array of `TokenTransfer` structs. It will do so only if the transaction is valid. ```solidity title="src/fdcExample/EVMTransaction.sol" struct TokenTransfer { address from; address to; uint256 value; } ``` The code of the contract is as follows. ```solidity title="src/fdcExample/EVMTransaction.sol" contract TransferEventListener is ITransferEventListener { TokenTransfer[] public tokenTransfers; address public USDC_CONTRACT = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238; // USDC contract address on sepolia function isEVMTransactionProofValid( IEVMTransaction.Proof calldata transaction ) public view returns (bool) { // Use the library to get the verifier contract and verify that this transaction was proved by the FDC IFdcVerification fdc = ContractRegistry.getFdcVerification(); return fdc.verifyEVMTransaction(transaction); } function collectTransferEvents( IEVMTransaction.Proof calldata _transaction ) external { // 1. FDC Logic // Check that this EVMTransaction has indeed been confirmed by the FDC require( isEVMTransactionProofValid(_transaction), "Invalid transaction proof" ); // 2. Business logic // Go through all events for ( uint256 i = 0; i < _transaction.data.responseBody.events.length; i++ ) { // Get current event IEVMTransaction.Event memory _event = _transaction .data .responseBody .events[i]; // Disregard events that are not from the USDC contract if (_event.emitterAddress != USDC_CONTRACT) { continue; } // Disregard non Transfer events if ( _event.topics.length == 0 || // No topics // The topic0 doesn't match the Transfer event _event.topics[0] != keccak256(abi.encodePacked("Transfer(address,address,uint256)")) ) { continue; } // We now know that this is a Transfer event from the USDC contract - and therefore know how to decode topics and data // Topic 1 is the sender address sender = address(uint160(uint256(_event.topics[1]))); // Topic 2 is the receiver address receiver = address(uint160(uint256(_event.topics[2]))); // Data is the amount uint256 value = abi.decode(_event.data, (uint256)); // Add the transfer to the list tokenTransfers.push( TokenTransfer({from: sender, to: receiver, value: value}) ); } } function getTokenTransfers() external view returns (TokenTransfer[] memory) { TokenTransfer[] memory result = new TokenTransfer[]( tokenTransfers.length ); for (uint256 i = 0; i < tokenTransfers.length; i++) { result[i] = tokenTransfers[i]; } return result; } } ``` ### Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If we proof is valid, the function `verifyEVMTransaction` will return `true`, otherwise `false`. We deploy and verify this contract with the `deployAndVerifyContract` function in the `scripts/fdcExample/EVMTransaction.ts` file. ```typescript title="scripts/fdcExample/EVMTransaction.ts" async function deployAndVerifyContract() { const args: any[] = []; const eventListener: TransferEventListenerInstance = await EVMTransaction.new( ...args, ); try { await run("verify:verify", { address: eventListener.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log("EVMTransaction deployed to", eventListener.address, "\n"); return eventListener; } ``` ## Interact with contract We define an additional function that allows us to interact with the just deployed contract. The `interactWithContract` function also takes the proof retrieved in the previous step as an argument. It abi decodes the `response_hex` value to an `IEVMTransaction.Response` struct. From that and the array of proofs, it constructs an `IEVMTransaction.Proof` object, on which it call the `registerAddress` function of the `AddressRegistry` contract deployed above. The contract verifies the address, and the script prints it to the console. ```typescript title="scripts/fdcExample/EVMTransaction.ts" async function interactWithContract( eventListener: TransferEventListenerInstance, proof: any, ) { console.log("Proof hex:", proof.response_hex, "\n"); // A piece of black magic that allows us to read the response type from an artifact const IEVMTransactionVerification = await artifacts.require( "IEVMTransactionVerification", ); const responseType = IEVMTransactionVerification._json.abi[0].inputs[0].components[1]; console.log("Response type:", responseType, "\n"); const decodedResponse = web3.eth.abi.decodeParameter( responseType, proof.response_hex, ); console.log("Decoded proof:", decodedResponse, "\n"); const transaction = await eventListener.collectTransferEvents({ merkleProof: proof.proof, data: decodedResponse, }); console.log("Transaction:", transaction.tx, "\n"); console.log("Token transfer:", await eventListener.tokenTransfers(0), "\n"); } ``` We can run the whole script by calling the following console command. ```bash yarn hardhat run scripts/fdcExample/EVMTransaction.ts --network coston2 ``` --- ## Payment(Hardhat) The `Payment` attestation type enables data collection about a transaction, classified as payment on the native chain. The currently supported chain are: `BTC`, `DOGE`, and `XRP`. You can learn more about it in the official [specification repo](/fdc/attestation-types/payment). We will define a `scripts/fdcExample/Payment.ts` file that will encapsulate the whole process. The shared helpers used below live in the starter under `scripts/utils/fdc.ts` and `scripts/utils/getters.ts`. ```typescript title="scripts/fdcExample/Payment.ts" prepareAttestationRequestBase, submitAttestationRequest, retrieveDataAndProofBase, } from "../utils/fdc"; const Payment = artifacts.require("PaymentRegistry"); const { VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL } = process.env; ... async function main() { const data = await prepareAttestationRequest(transactionId, inUtxo, utxo); console.log("Data:", data, "\n"); const abiEncodedRequest = data.abiEncodedRequest; const roundId = await submitAttestationRequest(abiEncodedRequest); const proof = await retrieveDataAndProof(abiEncodedRequest, roundId); const paymentRegistry: PaymentRegistryInstance = await deployAndVerifyContract(); await interactWithContract(paymentRegistry, proof); } main().then((data) => { process.exit(0); }); ``` The function names mostly mirror the steps described in the [FDC guide](/fdc/overview). To bridge the separate executions of the scripts, we will save the relevant data of each script to a file in the `dirPath` folder. Each succeeding script will then read that file to load the data. ## Prepare request In this guide we will demonstrate how to prepare an attestation request through a verifier server. At the end of the section we will provide a breakdown of the abi encoded request; thus we will demonstrate how it can be constructed manually. To use the verifier server, we send a request to its `prepareRequest` endpoint. A JSON request to the verifier follows the same structure for all attestation types, with field values varying between types. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `Payment`, `requestBody` is a JSON containing the fields: - `transactionId`: id of the transaction; as `bytes32` - `inUtxo`: UTXO chains support multiple source addresses, so this is the index of the address considered, as `uint256`; for non-UTXO chains this should always be `0` - `utxo`: UTXO chains support multiple receiving addresses, so this is the index of the address considered, as `uint256`; for non-UTXO chains this should always be `0` ### Reference Documentation - [Payment Specification](/fdc/attestation-types/payment) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/xrp/api-doc#/) - API available for [DOGE](https://fdc-verifiers-testnet.flare.network/verifier/doge/api-doc#/) and [BTC](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/). ### Example Values - `transactionId`: `C35B9D376DA75687E19780DB3A99B89CD8DFD451C842E51C7005048CE602464F` - `inUtxo`: non-default `0` - `utxo`: non-default `0` - `urlTypeBase`: string `xrp` - Replace `xrp` with `doge` or `btc` for other chains. ```typescript title="scripts/fdcExample/Payment.ts" // Request data const transactionId = "C35B9D376DA75687E19780DB3A99B89CD8DFD451C842E51C7005048CE602464F"; const inUtxo = "0"; const utxo = "0"; // Configuration constants const attestationTypeBase = "Payment"; const sourceIdBase = "testXRP"; const verifierUrlBase = VERIFIER_URL_TESTNET; const urlTypeBase = "xrp"; ``` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHex`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-hardhat-starter/blob/master/scripts/fdcExample/Base.ts) within the [example repository](https://github.com/flare-foundation/flare-hardhat-starter/), but they can also be defined locally in your contract or script. ```typescript title="scripts/utils/fdc.ts" function toHex(data: string) { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return result.padEnd(64, "0"); } ``` ```typescript title="scripts/utils/fdc.ts" function toUtf8HexString(data: string) { return "0x" + toHex(data); } ``` Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/btc_testnet4/api-doc#/Payment/BTCPaymentVerifierController_prepareRequest) to check what the response will be. The process of posting a request to a verifier server is identical for all attestation types. It differs only in values used. For that reason we define a base function that the `prepareAttestationRequest` function will call. The `prepareAttestationRequestBase` function formulates a request for the verifier server, and posts it to the given URL. ```typescript title="scripts/utils/fdc.ts" async function prepareAttestationRequestBase( url: string, apiKey: string, attestationTypeBase: string, sourceIdBase: string, requestBody: any, ) { console.log("Url:", url, "\n"); const attestationType = toUtf8HexString(attestationTypeBase); const sourceId = toUtf8HexString(sourceIdBase); const request = { attestationType: attestationType, sourceId: sourceId, requestBody: requestBody, }; console.log("Prepared request:\n", request, "\n"); const response = await fetch(url, { method: "POST", headers: { "X-API-KEY": apiKey, "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } console.log("Response status is OK\n"); return await response.json(); } ``` In the example repository, it is once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/scripts/fdcExample/Base.s.sol) library file. We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/` the path `verifier/btc_testnet4/Payment/prepareRequest`. If we were using another source, we would replace the string `eth` with `sgb` or `flr` accordingly (we would also have to replace `testETH` with `testSGB` or `testFLR`). Thus, the function that prepares the verifier request looks like: ```typescript title="scripts/fdcExample/Payment.ts" async function prepareAttestationRequest( transactionId: string, inUtxo: string, utxo: string, ) { const requestBody = { transactionId: transactionId, inUtxo: inUtxo, utxo: utxo, }; const url = `${verifierUrlBase}/verifier/${urlTypeBase}/Payment/prepareRequest`; const apiKey = VERIFIER_API_KEY_TESTNET ?? ""; return await prepareAttestationRequestBase( url, apiKey, attestationTypeBase, sourceIdBase, requestBody, ); } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 4164647265737356616c69646974790000000000000000000000000000000000 7465737442544300000000000000000000000000000000000000000000000000 7d2ef938d4ffd2392f588bf46563e07ab885b15fead91c1bb99b16f465b71a68 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000022 6d6739503966347772397737633173674665695443356f4d4c59584363326337 6873000000000000000000000000000000000000000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("Payment")` - **Second line:** `toUtf8HexString("testXRP")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `Payment.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. Now, we submit the validated request to the blockchain using deployed smart contracts. We will submit the validated request to the blockchain using deployed official Flare smart contracts. To streamline the process of accessing these, the [Flare smart contracts periphery package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) is shipped with the `ContractRegistry` library. The `IFlareContractRegistry` deployed at `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` on every Flare-family network resolves names to live contract addresses. We use it to discover: - `FdcHub`: for posting the request to - `FdcRequestFeeConfigurations`: calculates the fee of the request - `FlareSystemsManager`: for calculating the round ID - `Relay`: confirms the round has finalized - `FdcVerification`: exposes the FDC protocol id and verification entry points The starter resolves these from TypeScript via small wrappers in `scripts/utils/getters.ts` that call `IFlareContractRegistry.getContractAddressByName(...)` and return a typechain instance. To submit the attestation request, we first access the deployed `FdcHub` contract. We determine the fee for our attestation type, and then request the attestation of the FDC, paying the required fee. Lastly, we calculate the voting round Id from the transaction that carried the attestation request; we will need it to query the data and proof. ```typescript title="scripts/utils/fdc.ts" async function submitAttestationRequest(abiEncodedRequest: string) { const fdcHub = await getFdcHub(); const requestFee = await getFdcRequestFee(abiEncodedRequest); const transaction = await fdcHub.requestAttestation(abiEncodedRequest, { value: requestFee, }); console.log("Submitted request:", transaction.tx, "\n"); const roundId = await calculateRoundId(transaction); console.log( `Check round progress at: https://${hre.network.name}-systems-explorer.flare.rocks/voting-round/${roundId}?tab=fdc\n`, ); return roundId; } ``` {/* TODO */} ```typescript title="scripts/utils/getters.ts" async function getFdcHub() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcHubAddress: string = await registry.getContractAddressByName("FdcHub"); return await FdcHub.at(fdcHubAddress); } ``` The request fee is obtained from the `fdcRequestFeeConfigurations` contract. We once again resolve its address through the `IFlareContractRegistry`. ```typescript title="scripts/utils/fdc.ts" async function getFdcRequestFee(abiEncodedRequest: string) { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcRequestFeeConfigurationsAddress: string = await registry.getContractAddressByName("FdcRequestFeeConfigurations"); const fdcRequestFeeConfigurations: IFdcRequestFeeConfigurationsInstance = await FdcRequestFeeConfigurations.at(fdcRequestFeeConfigurationsAddress); return await fdcRequestFeeConfigurations.getRequestFee(abiEncodedRequest); } ``` The round ID is calculate from the timestamp of the block, containing the transaction requesting attestation. We first subtract from the block timestamp the timestamp of the first voting epoch. Then, we divide the number by the duration of the voting epoch (90 seconds). Instead of hard-coding them, we retrieve these values from another official Flare contract, the `flareSystemsManager`. ```typescript title="scripts/utils/fdc.ts" async function calculateRoundId(transaction: any) { const blockNumber = transaction.receipt.blockNumber; const block = await ethers.provider.getBlock(blockNumber); const blockTimestamp = BigInt(block!.timestamp); const flareSystemsManager: IFlareSystemsManagerInstance = await getFlareSystemsManager(); const firsVotingRoundStartTs = BigInt( await flareSystemsManager.firstVotingRoundStartTs(), ); const votingEpochDurationSeconds = BigInt( await flareSystemsManager.votingEpochDurationSeconds(), ); console.log("Block timestamp:", blockTimestamp, "\n"); console.log("First voting round start ts:", firsVotingRoundStartTs, "\n"); console.log( "Voting epoch duration seconds:", votingEpochDurationSeconds, "\n", ); const roundId = Number( (blockTimestamp - firsVotingRoundStartTs) / votingEpochDurationSeconds, ); console.log("Calculated round id:", roundId, "\n"); console.log( "Received round id:", Number(await flareSystemsManager.getCurrentVotingEpochId()), "\n", ); return roundId; } ``` We obtain the `flareSystemsManager` contract through the `IFlareContractRegistry`. ```typescript title="scripts/utils/getters.ts" async function getFlareSystemsManager() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const flareSystemsManagerAddress: string = await registry.getContractAddressByName("FlareSystemsManager"); return await FlareSystemsManager.at(flareSystemsManagerAddress); } ``` ## Retrieve data and proof To retrieve the data and proof, we must first wait for the voting round in which the attestation request was submitted to finalize; this takes no more than 180 seconds, but is on average much less. After the round has been finalized, we post a request to a DA Layer provider. We can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. You can learn more about how the FDC protocol works, check [here](/fdc/overview). Because the process includes waiting for the voting round to finalize, we prepare a `sleep` function. The function pauses the execution of the script for a given number of milliseconds. ```typescript title="scripts/utils/fdc.ts" function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } ``` The only difference between the `retrieveDataAndProof` functions of all six attestation types is the URL of the DA Layer server. For that reason, we will define as separate `retrieveDataAndProofBase` function that will handle most of the logic. The function waits for the round to finalize - rechecking every 10 seconds if necessary. Then, it prepares a proof request, and posts it to the DA Layer server. Because it might take a few seconds for the server to generate the proof, the function ensures that the response actually contains a sufficient response, and retries otherwise. ```typescript title="scripts/utils/fdc.ts" async function retrieveDataAndProofBase( url: string, abiEncodedRequest: string, roundId: number, ) { console.log("Waiting for the round to finalize..."); // We check every 10 seconds if the round is finalized const relay: IRelayInstance = await getRelay(); const fdcVerification: IFdcVerificationInstance = await getFdcVerification(); const protocolId = await fdcVerification.fdcProtocolId(); while (!(await relay.isFinalized(protocolId, roundId))) { await sleep(10000); } console.log("Round finalized!\n"); const request = { votingRoundId: roundId, requestBytes: abiEncodedRequest, }; console.log("Prepared request:\n", request, "\n"); await sleep(10000); var proof = await postRequestToDALayer(url, request, true); console.log("Waiting for the DA Layer to generate the proof..."); while (proof.response_hex == undefined) { await sleep(5000); proof = await postRequestToDALayer(url, request, false); } console.log("Proof generated!\n"); console.log("Proof:", proof, "\n"); return proof; } ``` We access the Flare's official `Relay` and `FdcVerification` contracts with helper functions. ```typescript title="scripts/utils/getters.ts" async function getRelay() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const relayAddress: string = await registry.getContractAddressByName("Relay"); return await IRelay.at(relayAddress); } async function getFdcVerification() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcVerificationAddress: string = await registry.getContractAddressByName("FdcVerification"); return await IFdcVerification.at(fdcVerificationAddress); } ``` The following function posts a proof request to the DA Layer. ```typescript title="scripts/utils/fdc.ts" async function postRequestToDALayer( url: string, request: any, watchStatus: boolean = false, ) { const response = await fetch(url, { method: "POST", headers: { // "X-API-KEY": "", "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (watchStatus && response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } else if (watchStatus) { console.log("Response status is OK\n"); } return await response.json(); } ``` The main prepare the URL of the DA Layer's `proof-by-request-raw` endpoint. We contact this specific endpoint, because it return the abi encoded `IPayment.Response` struct, and is thus unambiguous. ```typescript title="scripts/fdcExample/Payment.ts" async function retrieveDataAndProof( abiEncodedRequest: string, roundId: number, ) { const url = `${COSTON2_DA_LAYER_URL}/api/v1/fdc/proof-by-request-round-raw`; console.log("Url:", url, "\n"); return await retrieveDataAndProofBase(url, abiEncodedRequest, roundId); } ``` The response the DA Layer server returns has the following fields: - The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x4164647265737356616c69646974790000000000000000000000000000000000`. - The array `proofs` holds the Merkle proofs of our attestation request. - Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IPayment.Response` struct. We can ascertain the form of the proof request, as well as examine the response in advance, trough the [interactive documentation](https://ctn2-data-availability.flare.network/api-doc#/) of the DA Layer server.
An example complete proof response and decoded `IPayment.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 5061796d656e7400000000000000000000000000000000000000000000000000 7465737458525000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6c2d 00000000000000000000000000000000000000000000000000000000a019d806 2a3e7c7f6077b4d12207a9f063515eace70fbbf3c55514cd8bd659d4ab721447 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000048f822 0000000000000000000000000000000000000000000000000000000067ac9486 7f5b4967a9fbe9b447fed6d4e3699051516b6afe5f94db2e77ccf86470bfd74d a1475e9840d916c22f494c0dc25428d2affb5ae1f496efc82bbb59d46a336779 cd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82 cd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82 0000000000000000000000000000000000000000000000000000000005f5e10c 0000000000000000000000000000000000000000000000000000000005f5e10c 0000000000000000000000000000000000000000000000000000000005f5e100 0000000000000000000000000000000000000000000000000000000005f5e100 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000000', attestation_type: '0x5061796d656e7400000000000000000000000000000000000000000000000000', proof: [ '0xe1f98d39167eab17b2157c06efb80530b161d5eb15c439fc476e3242e30b3ac1', '0x23a8ffdb2cbaf0e2f3653923a159150f8d4c3ad5160f9e127cc9797ba233e6c2', '0xd756b90367b336e127f0759a1457825b4c2bf9011b71b56e15d9fcb7ff735ec8', '0xc881d1566868a986aef2bda47e9ab6dafeb8241bde5f5d53235837595829a5ea' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IPayment.Response` struct is: ```shell [ attestationType: '0x5061796d656e7400000000000000000000000000000000000000000000000000', sourceId: '0x7465737458525000000000000000000000000000000000000000000000000000', votingRound: '945197', lowestUsedTimestamp: '2686048262', requestBody: [ '0x2a3e7c7f6077b4d12207a9f063515eace70fbbf3c55514cd8bd659d4ab721447', '0', '0', transactionId: '0x2a3e7c7f6077b4d12207a9f063515eace70fbbf3c55514cd8bd659d4ab721447', inUtxo: '0', utxo: '0' ], responseBody: [ '4782114', '1739363462', '0x7f5b4967a9fbe9b447fed6d4e3699051516b6afe5f94db2e77ccf86470bfd74d', '0xa1475e9840d916c22f494c0dc25428d2affb5ae1f496efc82bbb59d46a336779', '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', '100000012', '100000012', '100000000', '100000000', '0x0000000000000000000000000000000000000000000000000000000000000000', true, '0', blockNumber: '4782114', blockTimestamp: '1739363462', sourceAddressHash: '0x7f5b4967a9fbe9b447fed6d4e3699051516b6afe5f94db2e77ccf86470bfd74d', sourceAddressesRoot: '0xa1475e9840d916c22f494c0dc25428d2affb5ae1f496efc82bbb59d46a336779', receivingAddressHash: '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', intendedReceivingAddressHash: '0xcd582d251987f15ecb29b69c2e02051479e84c176e39cbbdf04a4d0ef89bcf82', spentAmount: '100000012', intendedSpentAmount: '100000012', receivedAmount: '100000000', intendedReceivedAmount: '100000000', standardPaymentReference: '0x0000000000000000000000000000000000000000000000000000000000000000', oneToOne: true, status: '0' ] ] ```
## Use the data We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive data and proof of a Payment transaction, and store it into an array of special `Payment` structs. It will do so only if the transaction is valid. ```solidity title="src/fdcExample/Payment.sol" struct Payment { uint64 blockNumber; uint64 blockTimestamp; bytes32 sourceAddressHash; bytes32 receivingAddressHash; int256 spentAmount; bytes32 standardPaymentReference; uint8 status; } ``` The code for the contract as follows. ```solidity title="src/fdcExample/Payment.sol" contract PaymentRegistry is IPaymentRegistry { Payment[] public verifiedPayments; function isPaymentProofValid( IPayment.Proof calldata transaction ) public view returns (bool) { // Use the library to get the verifier contract and verify that this transaction was proved by the FDC IFdcVerification fdc = ContractRegistry.getFdcVerification(); return fdc.verifyPayment(transaction); } function registerPayment(IPayment.Proof calldata _transaction) external { // 1. FDC Logic // Check that this Payment has indeed been confirmed by the FDC require(isPaymentProofValid(_transaction), "Invalid transaction proof"); // 2. Business logic Payment memory provedPayment = Payment( _transaction.data.responseBody.blockNumber, _transaction.data.responseBody.blockTimestamp, _transaction.data.responseBody.sourceAddressHash, _transaction.data.responseBody.receivingAddressHash, _transaction.data.responseBody.spentAmount, _transaction.data.responseBody.standardPaymentReference, _transaction.data.responseBody.status ); verifiedPayments.push(provedPayment); } } ``` ### Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. We then access the `FdcVerification` contract through the `ContractRegistry`, and feed it the proof. If we proof is valid, the function `verifyPayment` will return `true`, otherwise `false`. We deploy and verify this contract with the `deployAndVerifyContract` function in the `scripts/fdcExample/Payment.ts` file. ```typescript title="scripts/fdcExample/Payment.ts" async function deployAndVerifyContract() { const args: any[] = []; const paymentRegistry: PaymentRegistryInstance = await Payment.new(...args); try { await run("verify:verify", { address: paymentRegistry.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log("Payment deployed to", paymentRegistry.address, "\n"); return paymentRegistry; } ``` ## Interact with contract We define an additional function that allows us to interact with the just deployed contract. The `interactWithContract` function also takes the proof retrieved in the previous step as an argument. It abi decodes the `response_hex` value to an `IPayment.Response` struct. From that and the array of proofs, it constructs an `IPayment.Proof` object, on which it call the `registerPayment` function of the `PaymentRegistry` contract deployed above. The contract verifies the payment, and the script prints it to the console. ```typescript title="scripts/fdcExample/Payment.ts" async function interactWithContract( paymentRegistry: PaymentRegistryInstance, proof: any, ) { console.log("Proof hex:", proof.response_hex, "\n"); // A piece of black magic that allows us to read the response type from an artifact const IPaymentVerification = await artifacts.require("IPaymentVerification"); const responseType = IPaymentVerification._json.abi[0].inputs[0].components[1]; console.log("Response type:", responseType, "\n"); const decodedResponse = web3.eth.abi.decodeParameter( responseType, proof.response_hex, ); console.log("Decoded proof:", decodedResponse, "\n"); const transaction = await paymentRegistry.registerPayment({ merkleProof: proof.proof, data: decodedResponse, }); console.log("Transaction:", transaction.tx, "\n"); console.log( "Verified payment:", await paymentRegistry.verifiedPayments(0), "\n", ); } ``` We can run the whole script by calling the following console command. ```bash yarn hardhat run scripts/fdcExample/Payment.ts --network coston2 ``` --- ## Web2Json(Hardhat) The `Web2Json` attestation type enables data collection from an arbitrary Web2 source, thought the source has to be whitelisted by the Flare Network in advance. You can learn more about it in the official [specification](/fdc/attestation-types/web2-json). We will now demonstrate how the FDC protocol can be used to collect the data of a given [Star Wars API](https://swapi.dev/) request. The request we will be making is `https://swapi.info/api/people/3`. The same procedure works for all public APIs. In this guide, we will follow the steps outlined in the [FDC overview](/fdc/overview). We will define a `scripts/fdcExample/Web2Json.ts` file that will encapsulate the whole process. The shared helpers used below live in the starter under `scripts/utils/fdc.ts` and `scripts/utils/getters.ts`. ```typescript title="scripts/fdcExample/Web2Json.ts" prepareAttestationRequestBase, submitAttestationRequest, retrieveDataAndProofBase, } from "../utils/fdc"; const StarWarsCharacterListV2 = artifacts.require("StarWarsCharacterListV2"); const { VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL } = process.env; ... async function main() { const data = await prepareAttestationRequest( apiUrl, postprocessJq, abiSignature ); console.log("Data:", data, "\n"); const abiEncodedRequest = data.abiEncodedRequest; const roundId = await submitAttestationRequest(abiEncodedRequest); const proof = await retrieveDataAndProof(abiEncodedRequest, roundId); const characterList: StarWarsCharacterListV2Instance = await deployAndVerifyContract(); await interactWithContract(characterList, proof); } void main().then((data) => { process.exit(0); }); ``` The function names mostly mirror the steps described in the [FDC guide](/fdc/overview). ## Prepare request In this guide we will demonstrate how to prepare an attestation request through a verifier server. At the end of the section we will provide a breakdown of the abi encoded request; thus we will demonstrate how it can be constructed manually. To use the verifier server, we send a request to its `prepareRequest` endpoint. A JSON request to the verifier follows the same structure for all attestation types, with field values varying between types. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `Web2Json`, `requestBody` is a JSON containing the fields: - `url`: url of the data source; as `string` - `httpMethod`: one of `GET`, `POST`, `PUT`, `PATCH` and `DELETE` - `headers`: request headers as a stringified JSON; `{}` if no headers are required (defaults to `{"Content-Type": "application/json"}`) - `queryParams`: request query parameters as a stringified JSON; `{}` if no query parameters are required, - `body`: request body as a stringified JSON; `{}` if no body is required, - `postProcessJq`: JQ filter to postprocess the json data received from the URL; as `string` - `abiSignature`: ABI signature of the Solidity struct that will be used to decode the data; as `string` ### Reference Documentation - [Web2Json Specification](/fdc/attestation-types/web2-json) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc#/) ### Example Values - `url`: the above address `https://swapi.info/api/people/3` - `httpMethod`: `GET` - `headers`: `{}`, which defaults to value `{"Content-Type": "application/json"}` - `queryParams`: `{}` - `body`: `{}` - `postProcessJq`: `{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}` - `abiSignature`: ```bash {"components": [ {"internalType": "string", "name": "name", "type": "string"}, {"internalType": "uint256", "name": "height", "type": "uint256"}, {"internalType": "uint256", "name": "mass", "type": "uint256"}, {"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"}, {"internalType": "uint256", "name": "uid", "type": "uint256"} ], "name": "task","type": "tuple"} ``` ```typescript title="scripts/fdcExample/Web2Json.ts" // Request data const apiUrl = "https://swapi.info/api/people/3"; const postProcessJq = `{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}`; const httpMethod = "GET"; const headers = "{}"; const queryParams = "{}"; const body = "{}"; const abiSignature = `{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}`; // Configuration constants const attestationTypeBase = "Web2Json"; const sourceIdBase = "PublicWeb2"; const verifierUrlBase = VERIFIER_URL_TESTNET; ``` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHex`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-hardhat-starter/blob/master/scripts/fdcExample/Base.ts) within the [example repository](https://github.com/flare-foundation/flare-hardhat-starter/), but they can also be defined locally in your contract or script. ```typescript title="scripts/utils/fdc.ts" function toHex(data: string) { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return result.padEnd(64, "0"); } ``` ```typescript title="scripts/utils/fdc.ts" function toUtf8HexString(data: string) { return "0x" + toHex(data); } ``` Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc/) to check the response. The process of posting a request to a verifier server is identical for all attestation types. It differs only in values used. For that reason we define a base function that the `prepareAttestationRequest` function will call. The `prepareAttestationRequestBase` function formulates a request for the verifier server, and posts it to the given URL. ```typescript title="scripts/utils/fdc.ts" async function prepareAttestationRequestBase( url: string, apiKey: string, attestationTypeBase: string, sourceIdBase: string, requestBody: any, ) { console.log("Url:", url, "\n"); const attestationType = toUtf8HexString(attestationTypeBase); const sourceId = toUtf8HexString(sourceIdBase); const request = { attestationType: attestationType, sourceId: sourceId, requestBody: requestBody, }; console.log("Prepared request:\n", request, "\n"); const response = await fetch(url, { method: "POST", headers: { "X-API-KEY": apiKey, "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } console.log("Response status is OK\n"); return await response.json(); } ``` In the example repository, it is once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/scripts/fdcExample/Base.s.sol) library file. {/* TODO: Web2Json verifier address */} We construct the URL by appending to the verifier address ``the path`Web2Json/prepareRequest`. Thus, the function that prepares the verifier request looks like: ```typescript title="scripts/fdcExample/Web2Json.ts" async function prepareAttestationRequest( apiUrl: string, postProcessJq: string, abiSignature: string, ) { const requestBody = { url: apiUrl, httpMethod: httpMethod, headers: headers, queryParams: queryParams, body: body, postProcessJq: postProcessJq, abiSignature: abiSignature, }; const url = `${verifierUrlBase}/verifier/web2/Web2Json/prepareRequest`; const apiKey = VERIFIER_API_KEY_TESTNET; return await prepareAttestationRequestBase( url, apiKey, attestationTypeBase, sourceIdBase, requestBody, ); } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 576562324a736f6e000000000000000000000000000000000000000000000000 5075626c69635765623200000000000000000000000000000000000000000000 64f3f301397c2dfa2f9043325e161596786109b9195520ca8623a6c6d46cf4b6 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000000000e0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000160 00000000000000000000000000000000000000000000000000000000000001a0 00000000000000000000000000000000000000000000000000000000000001e0 0000000000000000000000000000000000000000000000000000000000000220 00000000000000000000000000000000000000000000000000000000000002c0 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e696e666f2f6170692f70656f706c652f3300 0000000000000000000000000000000000000000000000000000000000000003 4745540000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d315d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("Web2Json")` - **Second line:** `toUtf8HexString("PublicWeb2")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `Web2Json.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. We will submit the validated request to the blockchain using deployed official Flare smart contracts. To streamline the process of accessing these, the [Flare smart contracts periphery package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) is shipped with the `ContractRegistry` library. The `IFlareContractRegistry` deployed at `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` on every Flare-family network resolves names to live contract addresses. We use it to discover: - `FdcHub`: for posting the request to - `FdcRequestFeeConfigurations`: calculates the fee of the request - `FlareSystemsManager`: for calculating the round ID - `Relay`: confirms the round has finalized - `FdcVerification`: exposes the FDC protocol id and verification entry points The starter resolves these from TypeScript via small wrappers in `scripts/utils/getters.ts` that call `IFlareContractRegistry.getContractAddressByName(...)` and return a typechain instance. To submit the attestation request, we first access the deployed `FdcHub` contract. We determine the fee for our attestation type, and then request the attestation of the FDC, paying the required fee. Lastly, we calculate the voting round Id from the transaction that carried the attestation request; we will need it to query the data and proof. ```typescript title="scripts/utils/fdc.ts" async function submitAttestationRequest(abiEncodedRequest: string) { const fdcHub = await getFdcHub(); const requestFee = await getFdcRequestFee(abiEncodedRequest); const transaction = await fdcHub.requestAttestation(abiEncodedRequest, { value: requestFee, }); console.log("Submitted request:", transaction.tx, "\n"); const roundId = await calculateRoundId(transaction); console.log( `Check round progress at: https://${hre.network.name}-systems-explorer.flare.rocks/voting-round/${roundId}?tab=fdc\n`, ); return roundId; } ``` {/* TODO */} ```typescript title="scripts/utils/getters.ts" async function getFdcHub() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcHubAddress: string = await registry.getContractAddressByName("FdcHub"); return await FdcHub.at(fdcHubAddress); } ``` The request fee is obtained from the `fdcRequestFeeConfigurations` contract. We once again resolve its address through the `IFlareContractRegistry`. ```typescript title="scripts/utils/fdc.ts" async function getFdcRequestFee(abiEncodedRequest: string) { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcRequestFeeConfigurationsAddress: string = await registry.getContractAddressByName("FdcRequestFeeConfigurations"); const fdcRequestFeeConfigurations: IFdcRequestFeeConfigurationsInstance = await FdcRequestFeeConfigurations.at(fdcRequestFeeConfigurationsAddress); return await fdcRequestFeeConfigurations.getRequestFee(abiEncodedRequest); } ``` The round ID is calculate from the timestamp of the block, containing the transaction requesting attestation. We first subtract from the block timestamp the timestamp of the first voting epoch. Then, we divide the number by the duration of the voting epoch (90 seconds). Instead of hard-coding them, we retrieve these values from another official Flare contract, the `flareSystemsManager`. ```typescript title="scripts/utils/fdc.ts" async function calculateRoundId(transaction: any) { const blockNumber = transaction.receipt.blockNumber; const block = await ethers.provider.getBlock(blockNumber); const blockTimestamp = BigInt(block!.timestamp); const flareSystemsManager: IFlareSystemsManagerInstance = await getFlareSystemsManager(); const firsVotingRoundStartTs = BigInt( await flareSystemsManager.firstVotingRoundStartTs(), ); const votingEpochDurationSeconds = BigInt( await flareSystemsManager.votingEpochDurationSeconds(), ); console.log("Block timestamp:", blockTimestamp, "\n"); console.log("First voting round start ts:", firsVotingRoundStartTs, "\n"); console.log( "Voting epoch duration seconds:", votingEpochDurationSeconds, "\n", ); const roundId = Number( (blockTimestamp - firsVotingRoundStartTs) / votingEpochDurationSeconds, ); console.log("Calculated round id:", roundId, "\n"); console.log( "Received round id:", Number(await flareSystemsManager.getCurrentVotingEpochId()), "\n", ); return roundId; } ``` We obtain the `flareSystemsManager` contract through the `IFlareContractRegistry`. ```typescript title="scripts/utils/getters.ts" async function getFlareSystemsManager() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const flareSystemsManagerAddress: string = await registry.getContractAddressByName("FlareSystemsManager"); return await FlareSystemsManager.at(flareSystemsManagerAddress); } ``` ## Retrieve data and proof To retrieve the data and proof, we must first wait for the voting round in which the attestation request was submitted to finalize; this takes no more than 180 seconds, but is on average much less. After the round has been finalized, we post a request to a DA Layer provider. We can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. If you want to learn more about how the FDC protocol works, check [here](/fdc/overview). Because the process includes waiting for the voting round to finalize, we prepare a `sleep` function. The function pauses the execution of the script for a given number of milliseconds. ```typescript title="scripts/utils/fdc.ts" function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } ``` The only difference between the `retrieveDataAndProof` functions of all six attestation types is the URL of the DA Layer server. For that reason, we will define as separate `retrieveDataAndProofBase` function that will handle most of the logic. The function waits for the round to finalize - rechecking every 10 seconds if necessary. Then, it prepares a proof request, and posts it to the DA Layer server. Because it might take a few seconds for the server to generate the proof, the function ensures that the response actually contains a sufficient response, and retries otherwise. ```typescript title="scripts/utils/fdc.ts" async function retrieveDataAndProofBase( url: string, abiEncodedRequest: string, roundId: number, ) { console.log("Waiting for the round to finalize..."); // We check every 10 seconds if the round is finalized const relay: IRelayInstance = await getRelay(); const fdcVerification: IFdcVerificationInstance = await getFdcVerification(); const protocolId = await fdcVerification.fdcProtocolId(); while (!(await relay.isFinalized(protocolId, roundId))) { await sleep(10000); } console.log("Round finalized!\n"); const request = { votingRoundId: roundId, requestBytes: abiEncodedRequest, }; console.log("Prepared request:\n", request, "\n"); await sleep(10000); var proof = await postRequestToDALayer(url, request, true); console.log("Waiting for the DA Layer to generate the proof..."); while (proof.response_hex == undefined) { await sleep(5000); proof = await postRequestToDALayer(url, request, false); } console.log("Proof generated!\n"); console.log("Proof:", proof, "\n"); return proof; } ``` We access the Flare's official `Relay` and `FdcVerification` contracts with helper functions. ```typescript title="scripts/utils/getters.ts" async function getRelay() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const relayAddress: string = await registry.getContractAddressByName("Relay"); return await IRelay.at(relayAddress); } async function getFdcVerification() { const FlareContractRegistry = artifacts.require("IFlareContractRegistry"); const registry: IFlareContractRegistryInstance = await FlareContractRegistry.at( "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", ); const fdcVerificationAddress: string = await registry.getContractAddressByName("FdcVerification"); return await IFdcVerification.at(fdcVerificationAddress); } ``` The following function posts a proof request to the DA Layer. ```typescript title="scripts/utils/fdc.ts" async function postRequestToDALayer( url: string, request: any, watchStatus: boolean = false, ) { const response = await fetch(url, { method: "POST", headers: { // "X-API-KEY": "", "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (watchStatus && response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } else if (watchStatus) { console.log("Response status is OK\n"); } return await response.json(); } ``` The main prepare the URL of the DA Layer's `proof-by-request-raw` endpoint. We contact this specific endpoint, because it return the abi encoded `IWeb2Json.Response` struct, and is thus unambiguous. ```typescript title="scripts/fdcExample/Web2Json.ts" async function retrieveDataAndProof( abiEncodedRequest: string, roundId: number, ) { const url = `${COSTON2_DA_LAYER_URL}/api/v1/fdc/proof-by-request-round-raw`; console.log("Url:", url, "\n"); return await retrieveDataAndProofBase(url, abiEncodedRequest, roundId); } ``` The response the DA Layer server returns has the following fields: - The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x4164647265737356616c69646974790000000000000000000000000000000000`. - The array `proofs` holds the Merkle proofs of our attestation request. - Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IWeb2Json.Response` struct. We can ascertain the form of the proof request, as well as examine the response in advance, trough the [interactive documentation](https://ctn2-data-availability.flare.network/api-doc#/) of the DA Layer server.
An example complete proof response and decoded `IWeb2Json.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 0000000000000000000000000000000000000000000000000000000000000020 576562324a736f6e000000000000000000000000000000000000000000000000 5075626c69635765623200000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000ef309 0000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c0 0000000000000000000000000000000000000000000000000000000000000520 00000000000000000000000000000000000000000000000000000000000000e0 0000000000000000000000000000000000000000000000000000000000000120 0000000000000000000000000000000000000000000000000000000000000160 00000000000000000000000000000000000000000000000000000000000001a0 00000000000000000000000000000000000000000000000000000000000001e0 0000000000000000000000000000000000000000000000000000000000000220 00000000000000000000000000000000000000000000000000000000000002c0 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e696e666f2f6170692f70656f706c652f3300 0000000000000000000000000000000000000000000000000000000000000003 4745540000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 7b7d000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d315d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000100 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000060 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000006 0000000000000000000000000000000000000000000000000000000000000003 0000000000000000000000000000000000000000000000000000000000000005 52322d4432000000000000000000000000000000000000000000000000000000', attestation_type: '0x576562324a736f6e000000000000000000000000000000000000000000000000', proof: [ '0x7f550d4fb5b312798757f8851bdaa92b3f6da403aa191b0a9d53bd7b03fc5b25', '0x7494ee278799510e6bbbcc5b1cff1c0da0fdca3531b1ca1e54b7d4a38e729bfc', '0xeab2d61332b9f1edceb7198357b737c2caf1e1b5abf8a3c86b205b29ecf6f08a', '0x962db69b54aba0bd4225b831d07080ab986e4fc1203a5a87c8302cf5e8509ea8' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IWeb2Json.Response` struct is: ```shell [ '0x576562324a736f6e000000000000000000000000000000000000000000000000', '0x5075626c69635765623200000000000000000000000000000000000000000000', '979721', '0', [ 'https://swapi.info/api/people/3', 'GET', '{}', '{}', '{}', '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}', '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}', url: 'https://swapi.info/api/people/3', httpMethod: 'GET', headers: '{}', queryParams: '{}', body: '{}', postProcessJq: '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}', abiSignature: '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}' ], [ '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000', abiEncodedData: '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000' ], attestationType: '0x576562324a736f6e000000000000000000000000000000000000000000000000', sourceId: '0x5075626c69635765623200000000000000000000000000000000000000000000', votingRound: '979721', lowestUsedTimestamp: '0', requestBody: [ 'https://swapi.info/api/people/3', 'GET', '{}', '{}', '{}', '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}', '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}', url: 'https://swapi.info/api/people/3', httpMethod: 'GET', headers: '{}', queryParams: '{}', body: '{}', postProcessJq: '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}', abiSignature: '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}' ], responseBody: [ '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000', abiEncodedData: '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000' ] ] ```
## Use the data {/* TODO */} We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive character data from the [Star Wars API](https://swapi.dev/), and store it in a `StarWarsCharacter` struct. It will do so only if the proof is valid. ```solidity title="src/fdcExample/Web2Json.sol" struct StarWarsCharacter { string name; uint256 numberOfMovies; uint256 apiUid; uint256 bmi; } ``` We will also need a `DataTransportObject` struct, that will allow us to decode the data. ```solidity title="src/fdcExample/Web2Json.sol" struct DataTransportObject { string name; uint256 height; uint256 mass; uint256 numberOfMovies; uint256 apiUid; } ``` The code of the contract is as follows. ```solidity title="src/fdcExample/Web2Json.sol" contract StarWarsCharacterList { mapping(uint256 => StarWarsCharacter) public characters; uint256[] public characterIds; function isWeb2JsonProofValid( IWeb2Json.Proof calldata _proof ) private view returns (bool) { // Inline the check for now until we have an official contract deployed return ContractRegistry.getFdcVerification().verifyWeb2Json( _proof ); } function addCharacter(IWeb2Json.Proof calldata data) public { require(isWeb2JsonProofValid(data), "Invalid proof"); DataTransportObject memory dto = abi.decode( data.data.responseBody.abiEncodedData, (DataTransportObject) ); require(characters[dto.apiUid].apiUid == 0, "Character already exists"); StarWarsCharacter memory character = StarWarsCharacter({ name: dto.name, numberOfMovies: dto.numberOfMovies, apiUid: dto.apiUid, bmi: (dto.mass * 100 * 100) / (dto.height * dto.height) }); characters[dto.apiUid] = character; characterIds.push(dto.apiUid); } function getAllCharacters() public view returns (StarWarsCharacter[] memory) { StarWarsCharacter[] memory result = new StarWarsCharacter[]( characterIds.length ); for (uint256 i = 0; i < characterIds.length; i++) { result[i] = characters[characterIds[i]]; } return result; } } ``` ### Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. If the proof is valid, the function `verifyWeb2Json` will return `true`, otherwise `false`. We deploy and verify this contract with the `deployAndVerifyContract` function in the `scripts/fdcExample/Web2Json.ts` file. ```typescript title="scripts/fdcExample/Web2Json.ts" async function deployAndVerifyContract() { const args: any[] = []; const characterList: StarWarsCharacterListInstance = await StarWarsCharacterList.new(...args); try { await run("verify:verify", { address: characterList.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log("StarWarsCharacterList deployed to", characterList.address, "\n"); return characterList; } ``` ## Interact with contract We define an additional function that allows us to interact with the just deployed contract. The `interactWithContract` function also takes the proof retrieved in the previous step as an argument. It abi decodes the `response_hex` value to an `IWeb2Json.Response` struct. From that and the array of proofs, it constructs an `IWeb2Json.Proof` object, on which it call the `registerAddress` function of the `AddressRegistry` contract deployed above. The contract verifies the address, and the script prints it to the console. ```typescript title="scripts/fdcExample/Web2Json.ts" async function interactWithContract( characterList: StarWarsCharacterListInstance, proof: any, ) { console.log("Proof hex:", proof.response_hex, "\n"); // A piece of black magic that allows us to read the response type from an artifact const IWeb2JsonVerification = await artifacts.require( "IWeb2JsonVerification", ); const responseType = IWeb2JsonVerification._json.abi[0].inputs[0].components[1]; console.log("Response type:", responseType, "\n"); const decodedResponse = web3.eth.abi.decodeParameter( responseType, proof.response_hex, ); console.log("Decoded proof:", decodedResponse, "\n"); const transaction = await characterList.addCharacter({ merkleProof: proof.proof, data: decodedResponse, }); console.log("Transaction:", transaction.tx, "\n"); console.log( "Star Wars Characters:\n", await characterList.getAllCharacters(), "\n", ); } ``` We can run the whole script by calling the following console command. ```bash yarn hardhat run scripts/fdcExample/Web2Json.ts --network coston2 ``` --- ## Proof of Reserves(Hardhat) This is a guide on how to build a simple dApp using the [Flare Data Connector](/fdc/overview). It demonstrates how multiple attestation types, namely the [EVMTransaction](/fdc/attestation-types/evm-transaction) and [Web2Json](/fdc/attestation-types/web2-json), can be combined within the same app. The app that we will be building is called `proofOfReserves`, which enables onchain verification that a stablecoin's circulating supply is backed by sufficient offchain reserves. We will first describe what issue the app is addressing, and then provide a detailed walkthrough through its source code. All the code for this project is available on GitHub, in the [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) repository. ## The problem Stablecoins are cryptographic tokens designed to maintain a fixed value, typically pegged to a fiat currency like the US dollar. To maintain trust in the system, the issuing institution must hold sufficient reserves to back the tokens in circulation. The `proofOfReserves` application demonstrates how to verify that a stablecoin issuer maintains adequate offchain dollar reserves to cover all tokens in circulation across multiple blockchains. This verification creates transparency and helps prevent situations where more tokens exist than the backing reserves can support. Implementing this verification system presents three technical challenges: 1. **Accessing offchain data**: We need to query a Web2 API that reports the institution's official dollar reserves. 2. **Reading onchain state**: We need to access the total token supply data from various blockchain networks. 3. **Cross-chain data collection**: We need to aggregate token supply information across multiple chains. The [Flare Data Connector (FDC)](/fdc/overview) provides solutions for both accessing Web2 APIs through the [Web2Json](/fdc/attestation-types/web2-json) attestation type and collecting cross-chain data via the [EVMTransaction](/fdc/attestation-types/evm-transaction) attestation type. For reading onchain state, we deploy a dedicated contract that reads the token supply and emits this data as an event. This guide will walk through all the components needed to build the complete `proofOfReserves` verification system. ## Smart Contract Architecture For our proof of reserves implementation, we'll create three distinct smart contracts: 1. `MyStablecoin`: A custom ERC20 token for testing 2. `TokenStateReader`: A utility contract that reads and broadcasts token supply data 3. `ProofOfReserves`: The main verification contract that processes attestation proofs Note that in a production environment, we would typically only need two contracts - the main verification contract and a state reader. However, since this is a guide and we want flexibility to experiment with different token supply values, we'll also deploy our own stablecoin. ### Stablecoin Contract Let's start with the stablecoin implementation. This contract creates an ERC20-compatible token with additional functionality for burning tokens and controlled minting. ```solidity title="contracts/proofOfReserves/Token.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract MyStablecoin is ERC20, ERC20Burnable, Ownable, ERC20Permit { constructor(address recipient, address initialOwner) ERC20("MyStablecoin", "MST") Ownable(initialOwner) ERC20Permit("MyStablecoin") { _mint(recipient, 666 * 10 ** decimals()); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } } ``` Because we are building our app around `@openzeppelin`'s ERC20 token, we can later replace the token with any such instance. This means that we can easily modify our app to work with an arbitrary contract that inherits the `ERC20`. ### TokenStateReader Contract The second contract we need to write is the one that reads the total token supply of a given token and emits an event that exposes both the token address and its total supply. It has a single method that takes an `ERC20` token instance and calls its `totalSupply` function. Then, it emits the `TotalTokenSupply` event with the token's address and total supply. ```solidity title="contracts/proofOfReserves/TokenStateReader.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract TokenStateReader { event TotalTokenSupply(address tokenAddress, uint256 totalSupply); function broadcastTokenSupply(ERC20 token) external returns (uint256) { emit TotalTokenSupply(address(token), token.totalSupply()); return token.totalSupply(); } } ``` ### ProofOfReserves Contract The final component in our implementation is the `ProofOfReserves` contract, which performs the actual verification of reserve adequacy. This contract evaluates whether the claimed dollar reserves are sufficient to back all tokens in circulation across different blockchains. The core functionality is contained in the `verifyReserves` function, which accepts two parameters: - An `IWeb2Json.Proof` struct containing attested data from the Web2 API about dollar reserves - An array of `IEVMTransaction.Proof` structs containing attested data about token supplies from various blockchains The function aggregates the total token supply from all chains and compares it against the claimed reserves. If sufficient reserves exist (i.e., if the total token supply is less than or equal to the claimed reserves), the function returns `true`; otherwise, it returns `false`. ```solidity title="contracts/proofOfReserves/ProofOfReserves.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; ... contract ProofOfReserves is Ownable { event GoodPair(address reader, address token, uint256 totalSupply); event BadPair(address reader, address token, uint256 totalSupply); uint256 public debugTokenReserves = 0; uint256 public debugClaimedReserves = 0; mapping(address => address) public tokenStateReaders; constructor() Ownable(msg.sender) {} function updateAddress(address readerAddress, address tokenAddress) public onlyOwner { tokenStateReaders[readerAddress] = tokenAddress; } function verifyReserves(IWeb2Json.Proof calldata jsonProof, IEVMTransaction.Proof[] calldata transactionProofs) external returns (bool) { uint256 claimedReserves = readReserves(jsonProof); uint256 totalTokenReserves = 0; for (uint256 i = 0; i < transactionProofs.length; i++) { totalTokenReserves += readReserves(transactionProofs[i]); } debugTokenReserves = totalTokenReserves; return totalTokenReserves <= (claimedReserves * 1 ether); } ... } ``` The contract defines several important components: - **Event Declarations**: The `GoodPair` and `BadPair` events are used for debugging and monitoring purposes, allowing us to trace token supply validation in block explorers like the [Coston2 Explorer](https://coston2-explorer.flare.network/). - **Debug Variables**: The `debugTokenReserves` and `debugClaimedReserves` state variables store the latest values from the verification process, providing transparency and easier troubleshooting. - **Registry Mapping**: The `tokenStateReaders` mapping serves as a registry that associates each `TokenStateReader` contract with its corresponding stablecoin token. This mapping ensures we only process events from authorized reader contracts. - **Access Control**: The `updateAddress` function, protected by the `onlyOwner` modifier, provides a secure mechanism to update the registry mapping. To properly decode data from the [Web2Json](/fdc/attestation-types/web2-json) attestation, we need to define a data structure that matches our expected format. Following patterns from the [Web2Json attestation type guide](/fdc/guides/hardhat/web2-json), we create a simple `DataTransportObject` struct: ```solidity title="contracts/proofOfReserves/ProofOfReserves.sol" struct DataTransportObject { uint256 reserves; } ``` This structure contains a single field to store the reserve amount received from the Web2 API. With this definition in place, we can now decode the `abiEncodedData` within the `IWeb2Json.Proof` struct and access its `reserves` field. Before accessing this data, we must first validate the proof - more on that later. For processing Web2Json proofs, we implement the following function: ```solidity title="contracts/proofOfReserves/ProofOfReserves.sol:ProofOfReserves" function readReserves(IWeb2Json.Proof calldata proof) private returns (uint256) { require(isValidProof(proof), "Invalid json proof"); DataTransportObject memory data = abi.decode(proof.data.responseBody.abiEncodedData, (DataTransportObject)); debugClaimedReserves = data.reserves; return data.reserves; } ``` The `readReserves` function for the `IEVMTransaction.Proof` type is more involved. It cycles through all transaction events and discards those that do not originate with the `tokenStateReader` contract. It also ignores the ones that belong to a wrong contract, according to the mapping. In the end, if all the conditions are met, the total supplies are added together. ```solidity title="contracts/proofOfReserves/ProofOfReserves.sol:ProofOfReserves" function readReserves(IEVMTransaction.Proof calldata proof) private returns (uint256) { require(isValidProof(proof), "Invalid transaction proof"); uint256 totalSupply = 0; for (uint256 i = 0; i < proof.data.responseBody.events.length; i++) { IEVMTransaction.Event memory _event = proof.data.responseBody.events[i]; address readerAddress = _event.emitterAddress; (address tokenAddress, uint256 supply) = abi.decode(_event.data, (address, uint256)); bool correctTokenAndReaderAddress = tokenStateReaders[readerAddress] == tokenAddress; if (correctTokenAndReaderAddress) { totalSupply += supply; emit GoodPair(readerAddress, tokenAddress, supply); } else { emit BadPair(readerAddress, tokenAddress, supply); } } return totalSupply; } ``` As for validating the proofs, the contracts that do that are quite simple. Using the `ContractRegistry` library they contact the `FdcVerification` contracts and call the appropriate verify functions. ```solidity title="contracts/proofOfReserves/ProofOfReserves.sol:ProofOfReserves" function isValidProof(IWeb2Json.Proof calldata proof) private view returns (bool) { return ContractRegistry.getFdcVerification().verifyWeb2Json(proof); } function isValidProof(IEVMTransaction.Proof calldata proof) private view returns (bool) { return ContractRegistry.getFdcVerification().verifyEVMTransaction(proof); } ``` :::info The `ContractRegistry` is a library shipped with the `flare-periphery-contracts` that allows for easier access to official Flare contracts. Instead of acquiring the needed contracts by their addresses, it exposes them through predefined functions. This approach is less error-prone. ::: There is one more thing we need to add before we can proceed to the second part of the guide. While not strictly necessary, it will make one of the future steps much easier. We will add an empty function that takes a `DataTransportObject` as input. This will allow us to read the abi signature of the `DataTransportObject` struct from the `ProofOfReserves` contract's artifact. ```solidity title="contracts/proofOfReserves/ProofOfReserves.sol:ProofOfReserves" function abiSignatureHack(DataTransportObject calldata dto) public pure {} ``` We name the function appropriately. ## Process Overview This guide demonstrates deployment on Flare's Coston and Coston2 testnets, but the same approach can be adapted for any EVM chain. The complete process follows these sequential steps: 1. Deploy and verify the `MyStablecoin` contract on both Coston and Coston2 chains 2. Deploy and verify the `TokenStateReader` contract on both Coston and Coston2 chains 3. Deploy and verify the `ProofOfReserves` contract on Coston2 chain only 4. Save `MyStablecoin`, `TokenStateReader`, and `ProofOfReserves` addresses to `scripts/proofOfReserves/config.ts` 5. Call the `broadcastTokenSupply` function of both `TokenStateReader` contracts with the corresponding `MyStablecoin` addresses 6. Save transaction hashes of both function calls to `scripts/proofOfReserves/config.ts` 7. Request attestation from the [FDC](/fdc/overview), and call `verifyReserves` function of the `ProofOfReserves` with the received data Throughout this guide, we'll provide separate scripts for each step above, with filenames that clearly indicate their purpose. :::warning While we deploy stablecoin and reader contracts on both chains, the `ProofOfReserves` contract is deployed only on the Coston2 chain, which serves as our verification hub. ::: ## Scripts The first three scripts each deploy and verify one of the contracts defined in the first part of the guide, ie. `MyStablecoin`, `TokenStateReader`, and `ProofOfReserves`. They are more or less the same script, the only real difference being the contracts deployed, and the arguments that are passed to their constructor. The `deployToken.ts` script deploys and verifies the `MyStablecoin` contract on the Coston and Coston2 chains. It declares the deployer address as a local `OWNER` constant — replace it with the address you control before running the script. ```typescript title="scripts/proofOfReserves/deployToken.ts" const MyStablecoin = artifacts.require("MyStablecoin"); const OWNER = "0xF5488132432118596fa13800B68df4C0fF25131d"; async function deployAndVerify() { const args: any[] = [OWNER, OWNER]; const myStablecoin: MyStablecoinInstance = await MyStablecoin.new(...args); try { await run("verify:verify", { address: myStablecoin.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log( `(${hre.network.name}) MyStablecoin deployed to`, myStablecoin.address, "\n", ); } deployAndVerify().then((data) => { process.exit(0); }); ``` We run this script with the command: ```sh yarn hardhat run scripts/proofOfReserves/deployToken.ts --network coston \ && yarn hardhat run scripts/proofOfReserves/deployToken.ts --network coston2 ``` The `deployTokenStateReader.ts` deploys and verifies the `TokenStateReader` contract on the Coston and Coston2 chain. ```typescript title="scripts/proofOfReserves/deployTokenStateReader.ts" const TokenStateReader = artifacts.require("TokenStateReader"); async function deployAndVerify() { const args: any[] = []; const tokenStateReader: TokenStateReaderInstance = await TokenStateReader.new( ...args, ); try { await run("verify:verify", { address: tokenStateReader.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log( `(${hre.network.name}) TokenStateReader deployed to`, tokenStateReader.address, "\n", ); } deployAndVerify().then((data) => { process.exit(0); }); ``` We run this script with the command: ```sh yarn hardhat run scripts/proofOfReserves/deployTokenStateReader.ts --network coston \ && yarn hardhat run scripts/proofOfReserves/deployTokenStateReader.ts --network coston2 ``` Finally, the `deployProofOfReserves.ts` script deploys and verifies the `ProofOfReserves` contract on Coston2 chain. ```typescript title="scripts/proofOfReserves/deployProofOfReserves.ts" const ProofOfReserves = artifacts.require("ProofOfReserves"); async function deployAndVerify() { const args: any[] = []; const proofOfReserves: ProofOfReservesInstance = await ProofOfReserves.new( ...args, ); try { await run("verify:verify", { address: proofOfReserves.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log( `(${hre.network.name}) ProofOfReserves deployed to`, proofOfReserves.address, "\n", ); } deployAndVerify().then((data) => { process.exit(0); }); ``` We run this script with the command: ```sh yarn hardhat run scripts/proofOfReserves/deployProofOfReserves.ts --network coston2 ``` After we run the three scripts, we save the contract addresses to the `scripts/proofOfReserves/config/` directory. Each network-specific value lives in its own file (one per token, reader, transaction hash, and the singleton `ProofOfReserves` address). `all.ts` re-exports the per-chain mappings used by the rest of the guide. ```typescript title="scripts/proofOfReserves/config/costonToken.ts" export const costonTokenAddress = "0x313f3dfBfF769816Fd1dEfc10700750d4F391504"; ``` ```typescript title="scripts/proofOfReserves/config/coston2Token.ts" export const coston2TokenAddress = "0x139a0741D26B724fdE6Ddd5aAc8968C172E3975E"; ``` ```typescript title="scripts/proofOfReserves/config/costonReader.ts" export const costonReaderAddress = "0xF118F45Fdb1a7E3Fa2Da5FFB102c1D3EBa97bEB0"; ``` ```typescript title="scripts/proofOfReserves/config/coston2Reader.ts" export const coston2ReaderAddress = "0x8d01566b34920E3523958b9649f0646F3F544Bf6"; ``` ```typescript title="scripts/proofOfReserves/config/proofOfReserves.ts" export const proofOfReservesAddress = "0x1052F13b01EA409Ea739563bb4dd842684BA531e"; ``` ```typescript title="scripts/proofOfReserves/config/all.ts" const tokenAddresses = new Map([ ["coston", costonTokenAddress], ["coston2", coston2TokenAddress], ]); const readerAddresses = new Map([ ["coston", costonReaderAddress], ["coston2", coston2ReaderAddress], ]); const transactionHashes = new Map([ ["coston", costonTransaction], ["coston2", coston2Transaction], ]); export { tokenAddresses, readerAddresses, proofOfReservesAddress, transactionHashes, }; ``` Each `*Transaction.ts` file holds the hash of the transaction where the corresponding `TokenStateReader` broadcast its supply. The illustrative starter values are: ```typescript title="scripts/proofOfReserves/config/costonTransaction.ts" export const costonTransaction = "0xc675cc308982a0300e744c937250fc5882c1eaca8266abc5a4fa5d8fa5319909"; ``` ```typescript title="scripts/proofOfReserves/config/coston2Transaction.ts" export const coston2Transaction = "0x5b5c7e45b3ec724e68add657df2923aebf86bb38ceac25f6fc4ad7c9969fedb2"; ``` The next script, `activateTokenStateReader`, calls the `broadcastTokenSupply` method of the `TokenStateReader` contracts on all chains. It retrieves the address mapping from the `scripts/proofOfReserves/config.ts` file and matches them to the current network (the `--network` parameter in the console command). ```typescript title="scripts/proofOfReserves/activateTokenStateReader.ts" const MyStablecoin = artifacts.require("MyStablecoin"); const TokenStateReader = artifacts.require("TokenStateReader"); async function main() { const network = hre.network.name; const tokenAddress = tokenAddresses.get(network); const readerAddress = readerAddresses.get(network); ); const transaction = await reader.broadcastTokenSupply(tokenAddress); console.log(`(${network}) Transaction id:`, transaction.tx, "\n"); } main().then((data) => { process.exit(0); }); ``` We run this script with the command: ```sh yarn hardhat run scripts/proofOfReserves/activateTokenStateReader.ts --network coston \ && yarn hardhat run scripts/proofOfReserves/activateTokenStateReader.ts --network coston2 ``` As discussed previously, the `TokenStateReader` contracts each emit an event containing the total token supply. We save the hashes of transactions where these events were emitted to `scripts/proofOfReserves/config/costonTransaction.ts` and `coston2Transaction.ts`; `all.ts` re-exports them as the `transactionHashes` map shown above. The last script, `verifyProofOfReserves.ts`, is the most advanced. It performs several steps that amount to the reserves being successfully or unsuccessfully verified. It first collects all the data and proofs and submits them to the `ProofOfReserves` contract. The steps are as follows: 1. preparing attestation requests 2. submitting attestation requests to FDC 3. waiting for all voting rounds to finalize 4. retrieving the data and proofs from the DA Layer 5. submitting the data and proofs to the `ProofOfReserves` contract We first import the addresses from `scripts/proofOfReserves/config/all.ts`, and certain settings from environmental variables. In these scripts, we also heavily utilize helper functions shipped with the `flare-hardhat-starter` repository (the `scripts/utils/fdc.ts` and `scripts/utils/getters.ts` files). For a detailed breakdown of these functions, look at the Hardhat attestation type guides. ```typescript title="scripts/proofOfReserves/verifyProofOfReserves.ts" prepareAttestationRequestBase, postRequestToDALayer, sleep, } from "../utils/fdc"; getFdcHub, getRelay, getFdcVerification, } from "../utils/getters"; getFdcRequestFee, calculateRoundId, } from "../utils/core"; tokenAddresses, readerAddresses, proofOfReservesAddress, transactionHashes, } from "./config/all"; const ProofOfReserves = artifacts.require("ProofOfReserves"); const { VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL, } = process.env; ... ``` Next, we define an `AttestationRequest` type. This will allow us to present the request data in a better organized format. Then, we prepare a list of requests, each populated with the data specified by its corresponding attestation type. The only two attestation types we need for this example are `IEVMTransaction` and `Web2Json`. For a more detailed explanation of the included fields, look at the appropriate type specification ([IEVMTransaction](/fdc/attestation-types/evm-transaction) and [Web2Json](/fdc/attestation-types/web2-json)). In this guide, we will be comparing the total supplies of previously deployed tokens (with an arbitrary initial supply of `666`), to the claimed reserves of a real stablecoin. We will obtain the dollar reserves from a Web2 endpoint that returns a JSON payload with a `value` field. The starter ships a Beeceptor mock at `https://mock-data.free.beeceptor.com/usdt0/reserves` for this purpose; substitute with the real reserves API of your choice once it is whitelisted by the Flare Network. To the response JSON, we will apply the following JQ filter. ```jq {reserves: .value | tonumber | floor} ``` The filter coerces the `value` string into a number and discards the fractional part, so that the value we receive is an integer suitable for on-chain encoding. We will encode the data as the `DataTransportObject` type, with the following abi signature (expanded to multiple lines for clarity's sake). ```typescript `{ \"components\": [{ \"internalType\": \"uint256\", \"name\": \"reserves\", \"type\": \"uint256\" }], \"internalType\": \"struct DataTransportObject\", \"name\": \"dto\", \"type\": \"tuple\" }`; ``` We have copied the abi signature from the Hardhat-generated artifact of the `abiSignatureHack` function of the `ProofOfReserves` contract. Since we have deployed the stablecoin contracts to Coston and Coston2, these will be the sources of the transactions. We read their addresses from the `scripts/proofOfReserves/config.ts` file. ```typescript title="scripts/proofOfReserves/verifyProofOfReserves.ts" ... type AttestationRequest = { source: string; sourceIdBase: string; verifierUrlBase: string; verifierApiKey: string; urlTypeBase: string; data: any; }; const requests: AttestationRequest[] = [ { source: "web2json", sourceIdBase: "PublicWeb2", verifierUrlBase: VERIFIER_URL_TESTNET, verifierApiKey: VERIFIER_API_KEY_TESTNET, urlTypeBase: "", data: { apiUrl: "https://mock-data.free.beeceptor.com/usdt0/reserves", httpMethod: "GET", headers: "{}", queryParams: "{}", body: "{}", postProcessJq: `{reserves: .value | tonumber | floor}`, abiSignature: `{"components": [{"internalType": "uint256","name": "reserves","type": "uint256"}],"internalType": "struct DataTransportObject","name": "dto","type": "tuple"}`, }, }, { source: "coston", sourceIdBase: "testSGB", verifierUrlBase: VERIFIER_URL_TESTNET!, verifierApiKey: VERIFIER_API_KEY_TESTNET!, urlTypeBase: "sgb", data: { transactionHash: transactionHashes.get("coston")!, }, }, { source: "coston2", sourceIdBase: "testFLR", verifierUrlBase: VERIFIER_URL_TESTNET!, verifierApiKey: VERIFIER_API_KEY_TESTNET!, urlTypeBase: "flr", data: { transactionHash: transactionHashes.get("coston2")!, }, }, ]; ... ``` If we wanted to include additional sources, we would specify them here. If the new source were a new chain, no further change to the code would be needed. We prepare all attestation requests using helper functions from the `flare-hardhat-starter` repository. We save the returned abi encoded data to a mapping from the source (`Web2Json`, `coston`, and `coston2`) to the data of the corresponding response. The procedure is enclosed within the `prepareAttestationRequests` function. ```typescript title="scripts/proofOfReserves/verifyProofOfReserves.ts" ... async function prepareAttestationRequests(transactions: AttestationRequest[]) { console.log("\nPreparing data...\n"); var data: Map = new Map(); for (const transaction of transactions) { console.log(`(${transaction.source})\n`); if (transaction.source === "web2json") { const responseData = await prepareWeb2JsonAttestationRequest(transaction); console.log("Data:", responseData, "\n"); data.set(transaction.source, responseData.abiEncodedRequest); } else { const responseData = await prepareTransactionAttestationRequest( transaction ); console.log("Data:", responseData, "\n"); data.set(transaction.source, responseData.abiEncodedRequest); } } return data; } ... ``` We then submit the abi encoded requests to the FDC, storing the round IDs of each submission to a mapping. To that end, we define the `submitAttestationRequests` function. ```typescript title="scripts/proofOfReserves/verifyProofOfReserves.ts" ... async function submitAttestationRequests(data: Map) { console.log("\nSubmitting attestation requests...\n"); const fdcHub = await getFdcHub(); var roundIds: Map = new Map(); for (const [source, abiEncodedRequest] of data.entries()) { console.log(`(${source})\n`); const requestFee = await getFdcRequestFee(abiEncodedRequest); const transaction = await fdcHub.requestAttestation(abiEncodedRequest, { value: requestFee, }); console.log("Submitted request:", transaction.tx, "\n"); const roundId = await calculateRoundId(transaction); console.log( `Check round progress at: https://${hre.network.name}-systems-explorer.flare.rocks/voting-round/${roundId}?tab=fdc\n` ); roundIds.set(source, roundId); } return roundIds; } ... ``` We wait for the voting rounds to finalize, checking every 10 seconds. Then we collect the data and proof, waiting an additional multiple of 10 seconds if the proof has not been generated yet. We do so for each source; again we save the result to a mapping. This logic is contained in the `retrieveDataAndProofs` function. ```typescript title="scripts/proofOfReserves/verifyProofOfReserves.ts" ... async function retrieveDataAndProofs( data: Map, roundIds: Map ) { console.log("\nRetrieving data and proofs...\n"); var proofs: Map = new Map(); const url = `${COSTON2_DA_LAYER_URL}/api/v1/fdc/proof-by-request-round-raw`; console.log("Url:", url, "\n"); for (const [source, roundId] of roundIds.entries()) { console.log(`(${source})\n`); console.log("Waiting for the round to finalize..."); // We check every 10 seconds if the round is finalized const relay: IRelayInstance = await getRelay(); const fdcVerification: IFdcVerificationInstance = await getFdcVerification(); const protocolId = await fdcVerification.fdcProtocolId(); while (!(await relay.isFinalized(protocolId, roundId))) { await sleep(10000); } console.log("Round finalized!\n"); const request = { votingRoundId: roundId, requestBytes: data.get(source), }; console.log("Prepared request:\n", request, "\n"); var proof = await postRequestToDALayer(url, request, true); console.log("Waiting for the DA Layer to generate the proof..."); while (proof.response_hex == undefined) { await sleep(10000); proof = await postRequestToDALayer(url, request, false); } console.log("Proof generated!\n"); console.log("Proof:", proof, "\n"); proofs.set(source, proof); } return proofs; } ... ``` Before the data can be used, we must decode it to an appropriate Solidity struct (`IEVMTransaction.Response` and `IWeb2Json.Response` respectively). We read the abi signature from Hardhat-generated artifacts (`IEVMTransactionVerification._json.abi[0].inputs[0].components[1]` and `IWeb2JsonVerification._json.abi[0].inputs[0].components[1]`). Afterwards, we save them as proof structs (`IEVMTransaction.Proof` and `IWeb2Json.Proof`). The logic is enclosed in `prepareDataAndProofs` function. ```typescript title="scripts/proofOfReserves/verifyProofOfReserves.ts" ... async function prepareDataAndProofs(data: Map) { const IWeb2JsonVerification = await artifacts.require("IWeb2JsonVerification"); const IEVMTransactionVerification = await artifacts.require( "IEVMTransactionVerification" ); const jsonProof = { merkleProof: data.get("web2json").proof, data: web3.eth.abi.decodeParameter( IWeb2JsonVerification._json.abi[0].inputs[0].components[1], data.get("web2json").response_hex ), }; var transactionProofs: any[] = []; for (const [source, proof] of data.entries()) { if (source !== "web2json") { const decodedProof = web3.eth.abi.decodeParameter( IEVMTransactionVerification._json.abi[0].inputs[0].components[1], proof.response_hex ); transactionProofs.push({ merkleProof: proof.proof, data: decodedProof, }); } } return [jsonProof, transactionProofs]; } ... ``` The final function, `submitDataAndProofsToProofOfReserves` function, interacts with the `ProofOfReserves` contract to verify stablecoin reserves. First, it accesses the latter at the specified address (imported from `scripts/proofOfReserves/config.ts`). It then updates the registered `MyStablecoin` and `TokenStateReader` addresses. Lastly, it submits all the data and proofs to the `ProofOfReserves` contract. ```typescript title="scripts/proofOfReserves/verifyProofOfReserves.ts" ... async function submitDataAndProofsToProofOfReserves(data: Map) { const proofOfReserves: ProofOfReservesInstance = await ProofOfReserves.at( proofOfReservesAddress ); for (const source of tokenAddresses.keys()) { await proofOfReserves.updateAddress( readerAddresses.get(source), tokenAddresses.get(source) ); } const [jsonProof, transactionProofs] = await prepareDataAndProofs(data); const sufficientReserves: boolean = await proofOfReserves.verifyReserves(jsonProof, transactionProofs); return sufficientReserves; } ... ``` We run the script with the command: ```sh yarn hardhat run scripts/proofOfReserves/verifyProofOfReserves.ts --network coston2 ``` The whole script is as follows: {VerifyReservesScript} --- ## Weather Insurance(Hardhat) In this guide, we will examine an example of a simple insurance dApp that uses the FDC's [Web2Json](/fdc/guides/hardhat/web2-json) attestation type. The dApp will allow users to create insurance policies for temperatures at specific coordinates falling below a specified threshold. Other users will be able to claim those policies, and the policies will be settled automatically by the contract. All the code described in this guide is available on GitHub, in the [Flare Hardhat starter](https://github.com/flare-foundation/flare-hardhat-starter) repository. ## The Process of Using the dApp To start with, let us describe the requirements for our Weather Insurance dApp. We will do so by laying out how we expect the users to interact with the main contract. There will be three actors involved in the process: - **policyholder**: the entity possessing assets that they want to insure against unfavorable weather conditions - **insurer**: the entity willing to take on the risk in exchange for a premium - **contract**: the smart contract that will handle the logic of the exchange With that, we can describe the process of using the Weather Insurance dApp. 1. The policyholder creates a policy, specifying: - Location of insured asset (latitude, longitude) - Policy duration (start and expiration timestamp) - Hourly rainfall threshold constituting the loss - Premium amount - Loss coverage amount They also deposit the premium to the contract that will resolve the policy. 2. The insurer accepts the policy and deposits the loss coverage amount to the contract. The contract pays out the deposited premium to the insurer. If no insurer has accepted the policy by the time it comes into effect (before its start timestamp), the policy is considered expired. {/* TODO rethink if the insurer should be allowed to accept the contract after it comes into effect */} 3. The policy is resolved. This happens in three ways: - The policy has expired because no insurer accepted it in the allotted time. The contract returns the premium to the policyholder. - An insurer has accepted the policy, and proof has been provided to the contract, demonstrating that a loss has occurred (in this case, that the temperature at the specified location fell below the agreed-upon threshold). The contract pays out the deposited loss coverage amount to the policyholder. - An insurer has accepted the policy, and the expiration timestamp has been reached without valid proof having been provided. The contract returns the loss coverage deposit to the insurer. With that we can now focus on the technical aspects of the procedure described above, starting with the main smart contract. ## The Contract The contract that we will define will be called `MinTempAgency`. This name coincides with the field name in the response on a call to the [OpenWeather](https://openweathermap.org/api) API, which we will be using to acquire weather data. We will use the [Flare Data Connector](/fdc/overview) to collect the weather data so, in keeping with the [Web2Json guide](/fdc/guides/hardhat/web2-json), we start by defining a `DataTransportObject` which will determine how the FDC should encode the data. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" struct DataTransportObject { int256 latitude; int256 longitude; string description; int256 temperature; int256 minTemp; uint256 windSpeed; uint256 windDeg; } ``` In its response, the API returns the latitude and longitude of the closest weather station to the desired coordinates. The policyholder is thus responsible for providing these, otherwise, they will not be able to prove that a loss has occurred. We store the weather station's latitude and longitude in the policy so that we can later confirm that the proof pertains to the correct location. The policy also includes a `minTemp` field, which will serve as the criterion for a loss. We save additional values to the policy, which may serve as an inspiration for defining additional policy types. At the top of the contract, we define a `Policy` struct, which will represent a policy once it has been registered. In addition to the values originating from the `DataTransportObject`, we declare the following fields: - **holder**: the address of the policyholder that created the policy - **premium**: the deposited premium for the policy - **coverage**: the expected loss coverage amount if the policy is accepted - **status**: a `PolicyStatus` enum value describing the state of the policy, with either of the following values: - **Unclaimed**: the policyholder has created the policy, but no insurer has accepted it yet - **Open**: an insurer has accepted the policy, but it has not been resolved yet - **Settled**: the policy has been resolved - **id**: a unique value identifying the policy The contract will save the policies to an array `registeredPolicies`. When a policy is accepted, the contract will save the insurer's address to the mapping `insurers`. We also define several events that will be emitted at different stages of a policy's lifetime. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" contract MinTempAgency { Policy[] public registeredPolicies; mapping(uint256 => address) insurers; enum PolicyStatus { Unclaimed, Open, Settled } struct Policy { address holder; int256 latitude; int256 longitude; uint256 startTimestamp; uint256 expirationTimestamp; int256 minTempThreshold; uint256 premium; uint256 coverage; PolicyStatus status; uint256 id; } event PolicyCreated(uint256 id); event PolicyClaimed(uint256 id); event PolicySettled(uint256 id); event PolicyExpired(uint256 id); event PolicyRetired(uint256 id); ... } ``` The function for policy creation requires a premium to be paid to the contract. The premium is thus not one of the parameters of this function. If no premium has been deposited, the function reverts. The function also ensures that the expiration timestamp is greater than the start timestamp. If these two checks are passed, a new `Policy` struct is created and added to the array of registered policies. A `PolicyCreated` event is emitted, with the policy ID as value. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function createPolicy( int256 latitude, int256 longitude, uint256 startTimestamp, uint256 expirationTimestamp, int256 minTempThreshold, uint256 coverage ) public payable { require(msg.value > 0, "No premium paid"); require(startTimestamp < expirationTimestamp, "Value of startTimestamp larger than expirationTimestamp"); Policy memory newPolicy = Policy({ holder: msg.sender, latitude: latitude, longitude: longitude, startTimestamp: startTimestamp, expirationTimestamp: expirationTimestamp, minTempThreshold: minTempThreshold, premium: msg.value, coverage: coverage, status: PolicyStatus.Unclaimed, id: registeredPolicies.length }); registeredPolicies.push(newPolicy); emit PolicyCreated(newPolicy.id); } ``` :::info Because Solidity does not support floating point numbers, we will store the fractional values as their `10^6` multiple. So, instead of Celsius, we will use micro-Celsius, instead of degrees for latitude and longitude micro-degrees, and so on. ::: The `claimPolicy` function first retrieves the policy with the given ID. It checks that the policy is still `Unclaimed`. If the policy's start timestamp has already passed, the function silently routes through `retireUnclaimedPolicy` (without reverting), so an insurer can never accidentally claim a stale policy. Just like the premium, the coverage value is also not a parameter but the amount paid to the contract. The function checks that it has received a sufficient amount before continuing. If all checks have passed, the policy's status is set to `Open`. The `registeredPolicies` array is updated, and the insurer added to the mapping `insurers`. Lastly, the premium is paid to the insurer, and a `PolicyClaimed` event is emitted. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function claimPolicy(uint256 id) public payable { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Unclaimed, "Policy already claimed"); if (block.timestamp > policy.startTimestamp) { retireUnclaimedPolicy(id); } require(msg.value >= policy.coverage, "Insufficient coverage paid"); policy.status = PolicyStatus.Open; registeredPolicies[id] = policy; insurers[id] = msg.sender; payable(msg.sender).transfer(policy.premium); emit PolicyClaimed(id); } ``` :::danger Any coin transfer must be performed only after the state has been updated. Otherwise, the contract is open for a reentrancy attack. ::: The code that resolves a policy has been extended to provide a better description of conditions that revert the function. The function first collects the policy with the provided ID, and checks that its status is `Open`. Then, it validates the provided proof with the `isWeb2JsonProofValid` helper function. If the proof is valid, the `resolvePolicy` function decodes the enclosed data to at `DataTransportObject` struct. Several checks follow. The first two ensure that we are currently within the time interval, described by the policy. We assume, that the data relates to the current weather conditions. For that reason, the function compares the timestamp of the current block to the policy's start and expiration timestamp. If the current timestamp is smaller than the start timestamp, the function reverts. If it exceeds the expiration timestamp, we expire the policy. Next, the function compares the coordinates provided by the proof to those of the policy, requiring they match. Lastly, it checks that the condition for a loss has been met; namely, that the minimum temperature in the proof falls below the threshold value set by the policy. Finally, if all checks have passed, the function marks the policy as `Settled`, and transfers the coverage amount to the policyholder. A `PolicySettled` event is emitted. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function resolvePolicy(uint256 id, IWeb2Json.Proof calldata proof) public { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Open, "Policy not open"); require(isWeb2JsonProofValid(proof), "Invalid proof"); DataTransportObject memory dto = abi.decode(proof.data.responseBody.abiEncodedData, (DataTransportObject)); require( block.timestamp >= policy.startTimestamp, string.concat( "Policy not yet in effect: ", Strings.toString(block.timestamp), " vs. ", Strings.toString(policy.startTimestamp) ) ); if (block.timestamp > policy.expirationTimestamp) { expirePolicy(id); return; } require( dto.latitude == policy.latitude && dto.longitude == policy.longitude, string.concat( "Invalid coordinates: ", Strings.toStringSigned(dto.latitude), ", ", Strings.toStringSigned(dto.longitude), " vs. ", Strings.toStringSigned(policy.latitude), ", ", Strings.toStringSigned(policy.longitude) ) ); require( dto.minTemp <= policy.minTempThreshold, string.concat( "Minimum temperature not met: ", Strings.toStringSigned(dto.minTemp), " vs. ", Strings.toStringSigned(policy.minTempThreshold) ) ); policy.status = PolicyStatus.Settled; registeredPolicies[id] = policy; payable(policy.holder).transfer(policy.coverage); emit PolicySettled(id); } ``` The `IWeb2Json.Proof` is validated as demonstrated in the [Web2Json guide](/fdc/guides/hardhat/web2-json). ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function isWeb2JsonProofValid(IWeb2Json.Proof calldata _proof) private view returns (bool) { return ContractRegistry.getFdcVerification().verifyWeb2Json(_proof); } ``` Despite being called from within the function that resolves a policy, the `expirePolicy` function can serve as a standalone function. For that reason, it performs the two checks for the policy's expiration again; it ensures its status is `Open`, and that the timestamp of the current block is greater than the expiration timestamp of the policy. If the checks pass, the policy is marked as `Settled`, and the coverage is returned to the insurer. A `PolicyExpired` event is emitted. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function expirePolicy(uint256 id) public { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Open, "Policy not open"); require(block.timestamp > policy.expirationTimestamp, "Policy not yet expired"); policy.status = PolicyStatus.Settled; registeredPolicies[id] = policy; payable(insurers[id]).transfer(policy.coverage); emit PolicyExpired(id); } ``` The last of the non-helper functions allows us to retire unclaimed policies. If the policy's status is `Unclaimed`, and the timestamp of the current block exceeds the policy's start timestamp, the policy is marked as `Settled`, and its premium is returned to the policyholder. A `PolicyRetired` event is emitted. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function retireUnclaimedPolicy(uint256 id) public { Policy memory policy = registeredPolicies[id]; require(policy.status == PolicyStatus.Unclaimed, "Policy not unclaimed"); require(block.timestamp > policy.startTimestamp, "Policy not yet expired"); policy.status = PolicyStatus.Settled; registeredPolicies[id] = policy; payable(policy.holder).transfer(policy.premium); emit PolicyRetired(id); } ``` The remaining functions serve a utility purpose. The `getInsurer` function allows us to query policy insurers, while the `getAllPolicies` returns the whole `registeredPolicies` array. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function getInsurer(uint256 id) public view returns (address) { return insurers[id]; } function getAllPolicies() public view returns (Policy[] memory) { return registeredPolicies; } ``` Like in the [Web2Json guide](/fdc/guides/hardhat/web2-json), we define an `abiSignatureHack` function, that will allow us to extract the ABI signature of the `DataTransportObject` from the Hardhat-generated artifacts. This will be important in the next section of this guide, where we will submit a Web2Json attestation request to the FDC. ```solidity title="contracts/weatherInsurance/MinTempAgency.sol" function abiSignatureHack(DataTransportObject memory dto) public pure {} ``` ## The Scripts The following scripts reflect the process described at the start of this guide. Most of them are straightforward, performing a single function call. The first script deploys the `MinTempAgency` contract and prints its address to the console. {DeployAgency} We save the address to a `scripts/weatherInsurance/minTemp/config.ts` file because we will access it in other scripts. ```typescript title="scripts/weatherInsurance/minTemp/config.ts" export const agencyAddress = "0x5Ac39E3DC1Bb32c21A0ab3eF45395207d45c8EB6"; ``` Next, we will create a new policy with the following parameters: - latitude: `46.419402127862405` - longitude: `15.587079308221126` - start timestamp: 30 seconds from now - expiration timestamp: an hour from now - minimum temperature threshold: 20 degrees Celsius (encoded as `20 * 10**6` micro-Celsius) - premium: `0.01 ether` - coverage: `0.1 ether` :::note We have set the minimum temperature threshold high enough that the policy will always be resolved successfully. ::: Because the response of the [OpenWeather](https://openweathermap.org/api) API includes not the provided coordinates, but those of the nearest weather station, we will first replace our latitude and longitude with those. This will ensure that the coordinates of the created policy match the ones in the proof data. Without this step, we could never prove that a loss has occurred. We find the correct coordinates by making a GET request to the OpenWeather API. To use the API, we need to provide it with an API key, which is available on a free account. We read the API key from the `.env` file. Also, we explicitly state that we are using metric units, although this is the default. The Web2Json verifier accepts the base URL and the query parameters as separate fields: `apiUrl` carries the bare endpoint and `queryParams` carries the per-request parameters as a JSON-encoded object. The starter encodes them like so: ```typescript const apiUrl = "https://api.openweathermap.org/data/2.5/weather"; const apiId = process.env.OPEN_WEATHER_API_KEY ?? ""; const units = "metric"; function prepareQueryParams(policy: any) { const queryParams = { lat: policy.latitude / 10 ** 6, lon: policy.longitude / 10 ** 6, units: units, appid: apiId, }; return JSON.stringify(queryParams); } ``` The script collects the appropriate coordinates as described above. It multiplies them with `10^6` to avoid the floating point values. Lastly, it creates a new policy by calling the `createPolicy` function on the `MinTempAgency` at the address specified in the `config.ts` file. {CreatePolicy} The script for claiming a policy has a helper function that calculates the policy coverage of a given policy. It then calls the `claimPolicy` function on the `MinTempAgency` at the address read from the `config.ts` file, paying the coverage value to the contract. In this example, the policy claimed has the ID of `0`. {ClaimPolicy} The script for resolving a policy is slightly more complicated. It involves making a Web2Json attestation request to the FDC and providing the returned proof to the `MinTempAgency` contract. To learn more about the Web2Json attestation request look at the [related guide](/fdc/guides/hardhat/web2-json), or its [spec](/fdc/attestation-types/web2-json). We prepare an attestation request by POSTing to the Web2Json verifier endpoint at `${VERIFIER_URL_TESTNET}/verifier/web2/Web2Json/prepareRequest`. The request body packages the `apiUrl`, `queryParams`, `postProcessJq` and `abiSignature` as separate fields so the verifier can compose the GET request itself. We need to specify the jq filter that the FDC should apply to the data received in the response to the POST request to the above URL. Only a few fields are needed, and most must first be multiplied by `10^6` so that we can store them as `uint256` values in Solidity. We also employ some IF-statements, to avoid the error of multiplying `null` values. The filter we will be using is: ```typescript title="scripts/weatherInsurance/minTemp/resolvePolicy.ts" const postProcessJq = `{ latitude: (.coord.lat | if . !== null then .*pow(10;6) else 0 end | floor), longitude: (.coord.lon | if . !== null then .*pow(10;6) else 0 end | floor), description: .weather[0].description, temperature: (.main.temp | if . !== null then .*pow(10;6) else 0 end | floor), minTemp: (.main.temp_min | if . !== null then .*pow(10;6) else 0 end | floor), windSpeed: (.wind.speed | if . !== null then . *pow(10;6) else 0 end | floor), windDeg: .wind.deg }`; ``` The last parameter missing is the ABI encoding for the FDC to store the processed data as. We extract it from the artifacts for the `MinTempAgency` generated by Hardhat, and more specifically from the parameters of the `abiSignatureHack` function. ```typescript title="scripts/weatherInsurance/minTemp/resolvePolicy.ts" const abiSignature = `{ \"components\": [ { \"internalType\": \"int256\", \"name\": \"latitude\", \"type\": \"int256\" }, { \"internalType\": \"int256\", \"name\": \"longitude\", \"type\": \"int256\" }, { \"internalType\": \"string\", \"name\": \"description\", \"type\": \"string\" }, { \"internalType\": \"int256\", \"name\": \"temperature\", \"type\": \"int256\" }, { \"internalType\": \"int256\", \"name\": \"minTemp\", \"type\": \"int256\" }, { \"internalType\": \"uint256\", \"name\": \"windSpeed\", \"type\": \"uint256\" }, { \"internalType\": \"uint256\", \"name\": \"windDeg\", \"type\": \"uint256\" } ], \"internalType\": \"struct DataTransportObject\", \"name\": \"dto\", \"type\": \"tuple\" }`; ``` The intermediate steps more or less follow the ones in the [Web2Json guide](/fdc/guides/hardhat/web2-json). We make a POST request to the verifier server and thus prepare the attestation request. We submit the ABI-encoded request to the FDC, wait for the current voting round to finalize, and then collect the data from a DA Layer server. Finally, we decode the data to an `IWeb2Json.Response` struct and add it to an `IWeb2Json.Proof` struct, which we input into the `resolvePolicy` function, along with the policy ID, of the `MinTempAgency` at the address from the `config.ts` file. The entire script looks as follows. {ResolvePolicy} The two remaining scripts handle the remaining two cases for a policy to reach the end of its lifetime. The first settles an `Open` policy that has reached its expiration timestamp. It does so by calling the `expirePolicy` function of the `MinTempAgency` at the address from the `config.ts` file. {ExpirePolicy} The second function settles an `Unclaimed` policy that has reached it expiration timestamp, through the `retireUnclaimedPolicy` of the `MinTempAgency` contract. {RetireUnclaimedPolicy} ## Modifications and enhancements In the last section of this guide, we will describe several options for improving the described example. We can diversify the offered policy types, which would require only a small adjustment to the existing code. But an even further improvement and a paradigm shift would be to issue tokens to insurers and policyholders. ### Additional data An example of how the policy types can be extended is provided in the [Flare Hardhat starter](https://github.com/flare-foundation/flare-hardhat-starter) repository. The `WeatherIdAgency` checks that the ID of the current weather, as described by the [OpenWeather specification](https://openweathermap.org/weather-conditions), matches or exceeds the weather code threshold within the policy. Other simple modification options are: - maximum temperature - atmospheric pressure - humidity - rainfall - snowfall - visibility - wind speed ### Issuing tokens Instead of running the insurance agency as a registry, we could issue tokens to represent the policies. One option would be to create an NFT when a policy is created, representing the policyholder's position, and a second NFT for the insurer's position when a policy is claimed. This would prevent either party's funds from being locked within a contract for the duration of the policy. Another option would be to issue ERC1155 tokens expressing the stake. It would enable the trade of fractions of policies while optimizing gas consumption. No doubt there is an even better token type for such use case. But that goes beyond the purpose of this guide. --- ## Web2Json for Custom API(Hardhat) The [Web2Json guide](/fdc/guides/hardhat/web2-json) demonstrates how the Flare Data Connector can be used to fetch Web2 and store it on the chain. The code for this and other examples is available within the [Flare Hardhat starter](https://github.com/flare-foundation/flare-hardhat-starter) repository. In this guide, we will see how the `Web2Json` example script within the Flare Hardhat starter can be modified to work with custom data and custom contracts. That way, the example code can serve as the base building block for a custom project. ## Necessary modifications In order to run on custom data, the example code needs to be modified in four places only. Those are: 1. The contract within the `contracts/fdcExample/Web2Json.sol` file. 2. The attestation request parameters at the top of the `scripts/fdcExample/Web2Json.ts`. In particular the parameters: - `apiUrl` - `postProcessJq` - `abiSignature` 3. The `deployAndVerifyContract` function within the `scripts/fdcExample/Web2Json.ts` file. 4. The `interactWithContract` function within the `scripts/fdcExample/Web2Json.ts` file. ## Contract The contract within the `contracts/fdcExample/Web2Json.sol` file should be changed to reflect the project goals. It could be omitted entirely and replaced with multiple files. The only requirement is that at least one contract - the one interacting with FDC - implements a function that accepts an `IWeb2Json.Proof` struct parameter. ## Attestation request parameters ```typescript title="scripts/fdcExample/Web2Json.ts" // Request data const apiUrl = "https://swapi.info/api/people/3"; const postProcessJq = `{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-1] | tonumber)}`; const httpMethod = "GET"; const headers = "{}"; const queryParams = "{}"; const body = "{}"; const abiSignature = `{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}`; ``` The attestation request parameters should describe the new Web2 source. The `apiUrl` is the URL of the API. It can additionally be configured with the `headers`, `queryParams`, and `body` fields. A different `httpMethod` can also be selected. The `postProcessJq` is the jq-filter that will be applied to the JSON data retrieved from the `apiUrl` API. It rearranges and modifies the input JSON into a new JSON. The `postProcessJq` filter can be workshopped using an online tool, like [The JQ Playground](https://play.jqlang.org). The `abiSignature` parameter determines how the modified JSON should be represented in Solidity. It is the ABI signature of an appropriate struct. The easiest way to acquire it is to create a `DataTransportObject` struct with the correct fields within some solidity file. Then, create a public dummy function that accepts a `DataTransportObject` parameter. After running `yarn hardhat compile` or `npx hardhat compile`, the contract artifact will be created within the `artifacts/contracts` directory. The dummy function can be searched for within the file of its parent contract, and the `abiSignature` read from its `inputs` field. ## Deploy and verify function ```typescript title="scripts/fdcExample/Web2Json.ts" async function deployAndVerifyContract() { const args: any[] = []; const characterList: MyContractInstance = await MyContract.new(...args); try { await run("verify:verify", { address: characterList.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log("MyContract deployed to", characterList.address, "\n"); return characterList; } ``` The `deployAndVerifyContract` function should be modified to deploy and verify the new contract. If the contract is deployed and verified by another script, and thus the script will only interact with an existing contract, the function should simply return that contract. ```typescript title="scripts/fdcExample/modifiedWeb2Json.ts" async function deployAndVerifyContract(address: string) { return MyContract.at(address); } ``` ## Interact with contract function ```typescript title="scripts/fdcExample/Web2Json.ts" async function interactWithContract( characterList: StarWarsCharacterListV2Instance, proof: any, ) { console.log("Proof hex:", proof.response_hex, "\n"); // A piece of black magic that allows us to read the response type from an artifact const IWeb2JsonVerification = await artifacts.require( "IWeb2JsonVerification", ); const responseType = IWeb2JsonVerification._json.abi[0].inputs[0].components[1]; console.log("Response type:", responseType, "\n"); const decodedResponse = web3.eth.abi.decodeParameter( responseType, proof.response_hex, ); console.log("Decoded proof:", decodedResponse, "\n"); const transaction = await characterList.addCharacter({ merkleProof: proof.proof, data: decodedResponse, }); console.log("Transaction:", transaction.tx, "\n"); console.log( "Star Wars Characters:\n", await characterList.getAllCharacters(), "\n", ); } ``` The `interactWithContract` function should be modified to interact with the new contract. Unless the dApp requires a more intricate interaction with the new contract, only the last few lines should be fundamentally changed. Likely, only the parameter type and the contract functions called should change. ```typescript title="scripts/fdcExample/modifiedWeb2Json.ts" async function interactWithContract(myContract: MyContractInstance, proof: any) { console.log("Proof hex:", proof.response_hex, "\n"); // A piece of black magic that allows us to read the response type from an artifact const IWeb2JsonVerification = await artifacts.require("IWeb2JsonVerification"); const responseType = IWeb2JsonVerification._json.abi[0].inputs[0].components[1]; console.log("Response type:", responseType, "\n"); const decodedResponse = web3.eth.abi.decodeParameter(responseType, proof.response_hex); console.log("Decoded proof:", decodedResponse, "\n"); // Only the below code fundamentally changed const transaction = await myContract.customFunction({ merkleProof: proof.proof, data: decodedResponse, }); console.log("Transaction:", transaction.tx, "\n"); ... } ``` --- ## Balance Decreasing This guide focuses on the [ConfirmedBlockHeightExists](/fdc/attestation-types/confirmed-block-height-exists) attestation type, an efficient way to assert whether a specific `blockNumber` is confirmed with additional data to compute the block production rate within a given time window. The primary contract interface for this attestation type is [`IConfirmedBlockHeightExists`](/fdc/reference/IFdcHub). ## Balance Decreasing Transaction [Full specification on GitHub](https://github.com/flare-foundation/songbird-state-connector-protocol/tree/main/specs/attestations/active-types). This attestation type is designed to prove that a transaction either decreases the balance of an address or is signed by the source address. One of the purposes of the Data Connector is to provide connectivity between different blockchains, allowing the use of information from one chain on another. Other chains may not have smart contract capability or support any kind of "fund locking" and unlocking based on conditions. This is where the Data Connector comes into play, allowing the Flare network to monitor (and police) an address on another chain and act upon changes in its balance. For instance, we can have an address on the Bitcoin network that acts as a vault (think fAssets). If the address owner violates an agreement by sending funds out, the Data Connector can detect it. To enhance security and avoid dependence on a single chain, this attestation type makes minimal assumptions about the violating transaction. A transaction is considered "offending" if the balance of the designated address is lower after the transaction or if the address is among the signers of the transaction (even if its balance is higher than before the transaction). This allows us to track balance decreases even if the change results from a complex transaction (e.g., multisig, complex scripts, or specific XRPL transactions where a non-participating address can have funds removed). The request body consists of only two arguments: - **`transactionId`**: The ID of the payment transaction we want to prove (same as with payment). - **`sourceAddressIndicator`**: The indicator of the address whose balance has been decreased. - On Bitcoin and Dogecoin, this is the index of the transaction input in hex, padded to a 0x prefixed 32-byte string (very similar to `inUtxo` in the payment type). - On XRPL, this is the standard address hash of the address whose balance we want to prove has decreased. Once the request is submitted, the verifiers will check the transaction, perform full accounting of the requested source address, and confirm the response if and only if the transaction indeed decreases the balance of the address or the address is among the signers of the transaction. In short, the request won't be confirmed if the balance stays the same and the address is not among the signers of the transaction, ensuring there are no false positives. If the address has indeed decreased the balance (or participated as a signer), the response will also contain information about when exactly the offending transaction occurred. The balance decrease might be allowed under certain conditions (e.g., after a certain time, or with the correct payment reference). The response will include the following information: - **`blockNumber`**: The number of the block in which the transaction is included. - **`blockTimestamp`**: The timestamp of the block in which the transaction is included. For UTXO chains, this is `mediantime`; for XRPL, this is `close_time` of the ledger. - **`sourceAddressHash`**: The standard address hash of the address indicated by the `sourceAddressIndicator`. For UTXO chains, this gives us the address that controlled the designated input. - **`spentAmount`**: The amount spent by the source address in minimal units. If this is negative, the address has received funds in the transaction but might still be among the signers. - **`standardPaymentReference`**: The standard payment reference of the transaction. This is useful if the transaction is an allowed payment and the payment reference is used to identify it. Let's see how the verification contract looks. ```solidity title="BalanceDecreasingTransactionVerification.sol" // SPDX-License-Identifier: MIT pragma solidity 0.8.20; contract BalanceDecreasingTransactionVerification is IBalanceDecreasingTransactionVerification { using MerkleProof for bytes32[]; IMerkleRootStorage public immutable merkleRootStorage; constructor(IMerkleRootStorage _merkleRootStorage) { merkleRootStorage = _merkleRootStorage; } function verifyBalanceDecreasingTransaction( BalanceDecreasingTransaction.Proof calldata _proof ) external view returns (bool _proved) { return _proof.data.attestationType == bytes32("BalanceDecreasingTransaction") && _proof.merkleProof.verify( merkleRootStorage.merkleRoot(_proof.data.votingRound), keccak256(abi.encode(_proof.data)) ); } } ``` If you remember the payment verification contract, this one is very similar. We still use the `MerkleProof` library to verify the proof, but the type we verify is different. We just ABI encode the response and hash it, and then we verify that the hash is included in the Merkle tree for the round—exactly the same way as with the payment type. All other types are very similar; only the type we verify is different. Importantly, the verification contract simply checks that this proof indeed proves that the structure we requested was included in a specific round. It does not make any assumptions about the response itself. The response itself should be checked by the dapp to ensure it is the expected one. In some cases, the verifiers will not confirm the response (as there is no such confirmation), but in this case, they might confirm the response and also indicate that the balance has not decreased (and has indeed increased). ### Example Showing a balance decreasing transaction is simple—we will reuse the script from creating a transaction and just prove that the transaction has indeed decreased the balance of the address. The complete code that produces the following example is present in `tryXRPLBalanceDecreasingTransaction.ts`. The code is practically the same as before; we just make the request to a different endpoint (due to the different attestation type), change the `attestationType` field in the request body, and specify the transaction and the address we want to prove the balance decrease for. As mentioned earlier, specifying the address is important since the address's balance might have decreased in the transaction, but its participation might have been minimal (or it was not even part of the initial signers). For UTXO chains, we also need to specify `sourceAddressIndicator` because many addresses might be involved in the transaction (by signing an array of outputs). We need to specify which one we want to prove the balance decrease for and request the verifiers to do the full accounting. ```typescript title="tryXRPLBalanceDecreasingTransaction.ts" const xrpl = require("xrpl"); const { XRPL_PRIVATE_KEY, ATTESTATION_URL, ATTESTATION_API_KEY, USE_TESTNET_ATTESTATIONS, } = process.env; const receiverAddress = "r9RLXvWuRro3RX33pk4xsN58tefYZ8Tvbj"; function toHex(data: string): string { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return "0x" + result.padEnd(64, "0"); } function fromHex(data: string): string { data = data.replace(/^(0x\.)/, ""); return data .split(/(\w\w)/g) .filter((p) => !!p) .map((c) => String.fromCharCode(parseInt(c, 16))) .join(""); } async function prepareAttestationResponse( attestationType: string, network: string, sourceId: string, requestBody: any, ): Promise { const response = await fetch( `${ATTESTATION_URL}/verifier/${network}/${attestationType}/prepareResponse`, { method: "POST", headers: { "X-API-KEY": ATTESTATION_API_KEY as string, "Content-Type": "application/json", }, body: JSON.stringify({ attestationType: toHex(attestationType), sourceId: toHex(sourceId), requestBody: requestBody, }), }, ); const data = await response.json(); return data; } async function getXRPLclient(): Promise { const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233"); await client.connect(); return client; } async function sendXRPLTransaction( message: string = "", amount: number = 10, target: string = "r9RLXvWuRro3RX33pk4xsN58tefYZ8Tvbj", ): Promise { const client = await getXRPLclient(); const test_wallet = xrpl.Wallet.fromSeed(XRPL_PRIVATE_KEY); let memos = []; if (message) { // Standard payment reference must be 32 bytes - so we right pad with 0 const MemoData = xrpl.convertStringToHex(message).padEnd(64, "0"); const MemoType = xrpl.convertStringToHex("Text"); const MemoFormat = xrpl.convertStringToHex("text/plain"); memos.push({ Memo: { MemoType: MemoType, MemoData: MemoData, MemoFormat: MemoFormat, }, }); } const transaction = await client.autofill({ TransactionType: "Payment", Account: test_wallet.address, Amount: amount.toString(), Destination: target, Memos: memos, }); const signed = test_wallet.sign(transaction); console.log( `See transaction at https://testnet.xrpl.org/transactions/${signed.hash}`, ); await client.submitAndWait(signed.tx_blob); await client.disconnect(); // sleep for 10 seconds to allow the transaction to be processed await new Promise((resolve) => setTimeout(resolve, 10 * 1000)); const result = await prepareAttestationResponse( "BalanceDecreasingTransaction", "xrp", "testXRP", { transactionId: "0x" + signed.hash, sourceAddressIndicator: web3.utils.soliditySha3(test_wallet.address), }, ); console.log(result); console.log(fromHex(result.response.responseBody.standardPaymentReference)); } async function main() { await sendXRPLTransaction("Hello world!"); } main().then(() => process.exit(0)); ``` You create a transaction, wait for it to be processed, and then prepare a response to check that it was indeed a balance decreasing transaction. An example response would look like this: ```json { "status": "VALID", "response": { "attestationType": "0x42616c616e636544656372656173696e675472616e73616374696f6e00000000", "sourceId": "0x7465737458525000000000000000000000000000000000000000000000000000", "votingRound": "0", "lowestUsedTimestamp": "1708671652", "requestBody": { "transactionId": "0xB40C7540D8393D389AAF6006C0429608ADD871C0CA3174B72EA55776D885B77B", "sourceAddressIndicator": "0xa1ca3089c3e9f4c6e9ccf2bfb65bcf3e9d7544a092c79d642d5d34a54e0267e1" }, "responseBody": { "blockNumber": "45629840", "blockTimestamp": "1708671652", "sourceAddressHash": "0xa1ca3089c3e9f4c6e9ccf2bfb65bcf3e9d7544a092c79d642d5d34a54e0267e1", "spentAmount": "22", "standardPaymentReference": "0x48656C6C6F20776F726C64210000000000000000000000000000000000000000" } } } Hello world! ``` All the fields are populated correctly. Most importantly, although the transaction sent 10 XRP drops, the response clearly shows that the balance decreased by 22 drops, as 12 drops were spent on the transaction fee. {/* */} --- ## Json Api (deprecated) :::danger Since May 2025 this guide is considered deprecated. The `JsonApi` attestation type has been update to a new version, `Web2Json`. You can find the updated version of this guide [here](/fdc/guides/hardhat/web2-json). ::: The `JsonApi` attestation type enables data collection from an arbitrary Web2 source. You can learn more about it in the official [specification repo](/fdc/attestation-types/json-api). We will now demonstrate how the FDC protocol can be used to collect the data of a given [Star Wars API](https://swapi.dev/) request. The request we will be making is `https://swapi.dev/api/peaople/3/`. The same procedure works for all public APIs. In this guide, we will follow the steps outlined in the [FDC overview](/fdc/overview). We will define a `scripts/fdcExample/JsonApi.ts` file that will encapsulate the whole process. ```typescript title="scripts/fdcExample/JsonApi.s.sol" prepareAttestationRequestBase, submitAttestationRequest, retrieveDataAndProofBase, } from "./Base"; const JsonApi = artifacts.require("TransferEventListener"); const { VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL } = process.env; ... async function main() { const data = await prepareAttestationRequest( apiUrl, postprocessJq, abiSignature ); console.log("Data:", data, "\n"); const abiEncodedRequest = data.abiEncodedRequest; const roundId = await submitAttestationRequest(abiEncodedRequest); const proof = await retrieveDataAndProof(abiEncodedRequest, roundId); const characterList: StarWarsCharacterListInstance = await deployAndVerifyContract(); await interactWithContract(characterList, proof); } main().then((data) => { process.exit(0); }); ``` The function names mostly mirror the steps described in the [FDC guide](/fdc/overview). ## Prepare request In this guide we will demonstrate how to prepare an attestation request through a verifier server. At the end of the section we will provide a breakdown of the abi encoded request; thus we will demonstrate how it can be constructed manually. To use the verifier server, we send a request to its `prepareRequest` endpoint. A JSON request to the verifier follows the same structure for all attestation types, with field values varying between types. ### Required Fields - `attestationType` is the UTF8 hex string encoding of the attestation type name, zero-padded to 32 bytes. - `sourceId` is the UTF8 hex string encoding of the data source identifier name, zero-padded to 32 bytes. - `requestBody` is different for each attestation type. In the case of `JsonApi`, `requestBody` is a JSON containing the fields: - `url`: url of the data source; as `string` - `postprocessJq`: JQ filter to postprocess the json data received from the URL; as `string` - `abi_signature`: ABI signature of the Solidity struct that will be used to decode the data; as `string` ### Reference Documentation - [JsonApi Specification](/fdc/attestation-types/json-api) - [Verifier Interactive Docs](https://fdc-verifiers-testnet.flare.network/verifier/web2/api-doc#/) ### Example Values - `url`: the above address `https://swapi.dev/api/people/3/` - `postprocessJq`: `{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split(\\"/\\") | .[-2] | tonumber)}` - `abi_signature`: ```bash {\\"components\\": [ {\\"internalType\\": \\"string\\", \\"name\\": \\"name\\", \\"type\\": \\"string\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"height\\", \\"type\\": \\"uint256\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"mass\\", \\"type\\": \\"uint256\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"numberOfFilms\\", \\"type\\": \\"uint256\\"}, {\\"internalType\\": \\"uint256\\", \\"name\\": \\"uid\\", \\"type\\": \\"uint256\\"} ], \\"name\\": \\"task\\",\\"type\\": \\"tuple\\"} ``` ```typescript title="scripts/fdcExample/JsonApi.ts" // Request data const apiUrl = "https://swapi.dev/api/people/3/"; const postprocessJq = `{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split(\"/\") | .[-2] | tonumber)}`; const abiSignature = `{\"components\": [{\"internalType\": \"string\", \"name\": \"name\", \"type\": \"string\"},{\"internalType\": \"uint256\", \"name\": \"height\", \"type\": \"uint256\"},{\"internalType\": \"uint256\", \"name\": \"mass\", \"type\": \"uint256\"},{\"internalType\": \"uint256\", \"name\": \"numberOfFilms\", \"type\": \"uint256\"},{\"internalType\": \"uint256\", \"name\": \"uid\", \"type\": \"uint256\"}],\"name\": \"task\",\"type\": \"tuple\"}`; // Configuration constants const attestationTypeBase = "IJsonApi"; const sourceIdBase = "WEB2"; const verifierUrlBase = JQ_VERIFIER_URL_TESTNET; ``` ### Encoding Functions To encode values into UTF8 hex: - `toUtf8HexString`: Converts a string to UTF8 hex. - `toHex`: Zero-right-pads the string to 32 bytes. These functions are included in the [Base library](https://github.com/flare-foundation/flare-hardhat-starter/blob/master/scripts/fdcExample/Base.ts) within the [example repository](https://github.com/flare-foundation/flare-hardhat-starter/), but they can also be defined locally in your contract or script. ```typescript title="scripts/fdcExample/Base.ts" function toHex(data: string) { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return result.padEnd(64, "0"); } ``` ```typescript title="scripts/fdcExample/Base.ts" function toUtf8HexString(data: string) { return "0x" + toHex(data); } ``` Because of the `console.log` commands it will produce JSON strings that represent valid requests; we can then pass this to the [interactive verifier](https://fdc-verifiers-testnet.flare.network/verifier/btc/api-doc#/JsonApi/BTCJsonApiVerifierController_prepareRequest) to check the response. The process of posting a request to a verifier server is identical for all attestation types. It differs only in values used. For that reason we define a base function that the `prepareAttestationRequest` function will call. The `prepareAttestationRequestBase` function formulates a request for the verifier server, and posts it to the given URL. ```typescript title="scripts/fdcExample/Base.ts" async function prepareAttestationRequestBase( url: string, apiKey: string, attestationTypeBase: string, sourceIdBase: string, requestBody: any, ) { console.log("Url:", url, "\n"); const attestationType = toUtf8HexString(attestationTypeBase); const sourceId = toUtf8HexString(sourceIdBase); const request = { attestationType: attestationType, sourceId: sourceId, requestBody: requestBody, }; console.log("Prepared request:\n", request, "\n"); const response = await fetch(url, { method: "POST", headers: { "X-API-KEY": apiKey, "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } console.log("Response status is OK\n"); return await response.json(); } ``` In the example repository, it is once again included within the [Base](https://github.com/flare-foundation/flare-foundry-starter/blob/master/scripts/fdcExample/Base.s.sol) library file. We construct the URL by appending to the verifier address `https://fdc-verifiers-testnet.flare.network/` the path `verifier/btc/JsonApi/prepareRequest`. If we were using another source, we would replace the string `eth` with `sgb` or `flr` accordingly (we would also have to replace `testETH` with `testSGB` or `testFLR`). Thus, the function that prepares the verifier request looks like: ```typescript title="scripts/fdcExample/JsonApi.ts" async function prepareAttestationRequest( apiUrl: string, postprocessJq: string, abiSignature: string, ) { const requestBody = { url: apiUrl, postprocessJq: postprocessJq, abi_signature: abiSignature, }; const url = `${verifierUrlBase}JsonApi/prepareRequest`; const apiKey = JQ_VERIFIER_API_KEY_TESTNET!; return await prepareAttestationRequestBase( url, apiKey, attestationTypeBase, sourceIdBase, requestBody, ); } ```
Understanding the `abiEncodedRequest`. If everything went right, the `abiEncodedRequest` should look something like this (minus the line breaks - we split it after the `0x` symbol and then after every 64 characters (32 bytes), for the sake of clarity). ``` 0x 494a736f6e417069000000000000000000000000000000000000000000000000 5745423200000000000000000000000000000000000000000000000000000000 0b62b2fe7066a5b56cd4cc859f4c802a02e2a0f84b5ad12893ef5a90651e588c 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000060 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000140 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e6465762f6170692f70656f706c652f332f00 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d325d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 ``` Let's break it down line by line: - **First line:** `toUtf8HexString("JsonApi")` - **Second line:** `toUtf8HexString("testETH")` - **Third line:** message integrity code (MIC), a hash of the whole response salted with a string `"Flare"`, ensures the integrity of the attestation - **Remaining lines:** ABI encoded `JsonApi.RequestBody` Solidity struct What this demonstrates is that, with some effort, the `abiEncodedRequest` can be constructed manually.
## Submit request to FDC This step transitions from offchain request preparation to onchain interaction with the FDC protocol. We will submit the validated request to the blockchain using deployed official Flare smart contracts. To streamline the process of accessing these, the [Flare smart contracts periphery package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) is shipped with the `ContractRegistry` library. The `ContractRegistry` library allows us to {/* TODO contract registry */} We define a `Helpers` contract that will give us access to the following contracts: - `FdcHub`: for posting the request to - `FdcRequestFeeConfigurations`: calculates the fee of the request - `FlareSystemsManager`: for calculating the round ID - `Relay`: confirms the round has finalized ```solidity title="contracts/fdcExample/Helpers.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; "@flarenetwork/flare-periphery-contracts/coston2/IFdcRequestFeeConfigurations.sol"; contract Helpers { function getFdcHub() public view returns (IFdcHub) { return ContractRegistry.getFdcHub(); } function getFdcRequestFeeConfigurations() public view returns (IFdcRequestFeeConfigurations) { return ContractRegistry.getFdcRequestFeeConfigurations(); } function getFlareSystemsManager() public view returns (IFlareSystemsManager) { return ContractRegistry.getFlareSystemsManager(); } function getRelay() public view returns (IRelay) { return ContractRegistry.getRelay(); } } ``` We expose the `Helpers` contract through the following function. ```typescript title="scripts/fdcExample/Base.ts" async function getHelpers() { const helpers: HelpersInstance = await Helpers.new(); return helpers; } ``` To submit the attestation request, we first access the deployed `FdcHub` contract. We determine the fee for our attestation type, and then request the attestation of the FDC, paying the required fee. Lastly, we calculate the voting round Id from the transaction that carried the attestation request; we will need it to query the data and proof. ```typescript title="scripts/fdcExample/Base.ts" async function submitAttestationRequest(abiEncodedRequest: string) { const fdcHub = await getFdcHub(); const requestFee = await getFdcRequestFee(abiEncodedRequest); const transaction = await fdcHub.requestAttestation(abiEncodedRequest, { value: requestFee, }); console.log("Submitted request:", transaction.tx, "\n"); const roundId = await calculateRoundId(transaction); console.log( `Check round progress at: https://${hre.network.name}-systems-explorer.flare.rocks/voting-epoch/${roundId}?tab=fdc\n`, ); return roundId; } ``` {/* TODO */} ```typescript title="scripts/fdcExample/Base.ts" async function getFdcHub() { const helpers: HelpersInstance = await getHelpers(); const fdcHubAddress: string = await helpers.getFdcHub(); return await FdcHub.at(fdcHubAddress); } ``` The request fee is obtained from the `fdcRequestFeeConfigurations` contract. We once again connect to the `fdcRequestFeeConfigurations` contract through the `ContractRegistry` library. ```typescript title="scripts/fdcExample/Base.ts" async function getFdcRequestFee(abiEncodedRequest: string) { const helpers: HelpersInstance = await getHelpers(); const fdcRequestFeeConfigurationsAddress: string = await helpers.getFdcRequestFeeConfigurations(); const fdcRequestFeeConfigurations: IFdcRequestFeeConfigurationsInstance = await FdcRequestFeeConfigurations.at(fdcRequestFeeConfigurationsAddress); return await fdcRequestFeeConfigurations.getRequestFee(abiEncodedRequest); } ``` The round ID is calculate from the timestamp of the block, containing the transaction requesting attestation. We first subtract from the block timestamp the timestamp of the first voting epoch. Then, we divide the number by the duration of the voting epoch (90 seconds). Instead of hard-coding them, we retrieve these values from another official Flare contract, the `flareSystemsManager`. ```typescript title="scripts/fdcExample/Base.ts" async function calculateRoundId(transaction: any) { const blockNumber = transaction.receipt.blockNumber; const block = await ethers.provider.getBlock(blockNumber); const blockTimestamp = BigInt(block!.timestamp); const flareSystemsManager: IFlareSystemsManagerInstance = await getFlareSystemsManager(); const firsVotingRoundStartTs = BigInt( await flareSystemsManager.firstVotingRoundStartTs(), ); const votingEpochDurationSeconds = BigInt( await flareSystemsManager.votingEpochDurationSeconds(), ); console.log("Block timestamp:", blockTimestamp, "\n"); console.log("First voting round start ts:", firsVotingRoundStartTs, "\n"); console.log( "Voting epoch duration seconds:", votingEpochDurationSeconds, "\n", ); const roundId = Number( (blockTimestamp - firsVotingRoundStartTs) / votingEpochDurationSeconds, ); console.log("Calculated round id:", roundId, "\n"); console.log( "Received round id:", Number(await flareSystemsManager.getCurrentVotingEpochId()), "\n", ); return roundId; } ``` We obtain the `flareSystemsManager` contract through the `ContractRegistry` library and the previously defined `Helpers` contract as well. ```typescript title="scripts/fdcExample/Base.ts" async function getFlareSystemsManager() { const helpers: HelpersInstance = await getHelpers(); const flareSystemsManagerAddress: string = await helpers.getFlareSystemsManager(); return await FlareSystemsManager.at(flareSystemsManagerAddress); } ``` ## Retrieve data and proof To retrieve the data and proof, we must first wait for the voting round in which the attestation request was submitted to finalize; this takes no more than 180 seconds, but is on average much less. After the round has been finalized, we post a request to a DA Layer provider. We can check if the request was submitted successfully on the [AttestationRequests](https://coston2-systems-explorer.flare.rocks/attestation-request) page on the Flare Systems Explorer website. To check if the round has been finalized, go to [Finalizations](https://coston2-systems-explorer.flare.rocks/finalizations) page. If you want to learn more about how the FDC protocol works, check [here](/fdc/overview). Because the process includes waiting for the voting round to finalize, we prepare a `sleep` function. The function pauses the execution of the script for a given number of milliseconds. ```typescript title="scripts/fdcExample/Base.ts" function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } ``` The only difference between the `retrieveDataAndProof` functions of all six attestation types is the URL of the DA Layer server. For that reason, we will define as separate `retrieveDataAndProofBase` function that will handle most of the logic. The function waits for the round to finalize - rechecking every 10 seconds if necessary. Then, it prepares a proof request, and posts it to the DA Layer server. Because it might take a few seconds for the server to generate the proof, the function ensures that the response actually contains a sufficient response, and retries otherwise. ```typescript title="scripts/fdcExample/Base.ts" async function retrieveDataAndProofBase( url: string, abiEncodedRequest: string, roundId: number, ) { console.log("Waiting for the round to finalize..."); // We check every 10 seconds if the round is finalized const relay: IRelayInstance = await getRelay(); while (!(await relay.isFinalized(200, roundId))) { await sleep(10000); } console.log("Round finalized!\n"); const request = { votingRoundId: roundId, requestBytes: abiEncodedRequest, }; console.log("Prepared request:\n", request, "\n"); await sleep(10000); var proof = await postRequestToDALayer(url, request, true); console.log("Waiting for the DA Layer to generate the proof..."); while (proof.response_hex == undefined) { await sleep(5000); proof = await postRequestToDALayer(url, request, false); } console.log("Proof generated!\n"); console.log("Proof:", proof, "\n"); return proof; } ``` We access the Flare's official `Relay` contract with a helper function. ```typescript title="scripts/fdcExample/Base.ts" async function getRelay() { const helpers: HelpersInstance = await getHelpers(); const relayAddress: string = await helpers.getRelay(); return await IRelay.at(relayAddress); } ``` The following function posts a proof request to the DA Layer. ```typescript title="scripts/fdcExample/Base.ts" async function postRequestToDALayer( url: string, request: any, watchStatus: boolean = false, ) { const response = await fetch(url, { method: "POST", headers: { // "X-API-KEY": "", "Content-Type": "application/json", }, body: JSON.stringify(request), }); if (watchStatus && response.status != 200) { throw new Error( `Response status is not OK, status ${response.status} ${response.statusText}\n`, ); } else if (watchStatus) { console.log("Response status is OK\n"); } return await response.json(); } ``` The main prepare the URL of the DA Layer's `proof-by-request-raw` endpoint. We contact this specific endpoint, because it return the abi encoded `IJsonApi.Response` struct, and is thus unambiguous. ```typescript title="scripts/fdcExample/JsonApi.ts" async function retrieveDataAndProof( abiEncodedRequest: string, roundId: number, ) { const url = `${COSTON2_DA_LAYER_URL}api/v1/fdc/proof-by-request-round-raw`; console.log("Url:", url, "\n"); return await retrieveDataAndProofBase(url, abiEncodedRequest, roundId); } ``` The response the DA Layer server returns has the following fields: - The field `attestationType` holds the UTF8 encoded hex string of the attestation type name, padded to 32 bytes. Thus, it should match the value of the `attestationType` parameter in the Prepare the request step. In our case, that value is `0x4164647265737356616c69646974790000000000000000000000000000000000`. - The array `proofs` holds the Merkle proofs of our attestation request. - Lastly, `responseHex` is the ABI encoding of the chosen attestation type response struct. In this case, it is the `IJsonApi.Response` struct. We can ascertain the form of the proof request, as well as examine the response in advance, trough the [interactive documentation](https://fdc-verifiers-testnet.flare.network/verifier/btc/api-doc) of the DA Layer server.
An example complete proof response and decoded `IJsonApi.Response`. An example DA Layer response for a request using the data provided in this example is: ```shell { response_hex: '0x 0000000000000000000000000000000000000000000000000000000000000020 494a736f6e417069000000000000000000000000000000000000000000000000 5745423200000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000e6c41 0000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000c0 00000000000000000000000000000000000000000000000000000000000003a0 0000000000000000000000000000000000000000000000000000000000000060 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000140 000000000000000000000000000000000000000000000000000000000000001f 68747470733a2f2f73776170692e6465762f6170692f70656f706c652f332f00 0000000000000000000000000000000000000000000000000000000000000078 7b6e616d653a202e6e616d652c206865696768743a202e6865696768742c206d 6173733a202e6d6173732c206e756d6265724f6646696c6d733a202e66696c6d 73207c206c656e6774682c207569643a20282e75726c207c2073706c69742822 2f2229207c202e5b2d325d207c20746f6e756d626572297d0000000000000000 0000000000000000000000000000000000000000000000000000000000000173 7b22636f6d706f6e656e7473223a205b7b22696e7465726e616c54797065223a 2022737472696e67222c20226e616d65223a20226e616d65222c202274797065 223a2022737472696e67227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a2022686569676874222c202274797065 223a202275696e74323536227d2c7b22696e7465726e616c54797065223a2022 75696e74323536222c20226e616d65223a20226d617373222c20227479706522 3a202275696e74323536227d2c7b22696e7465726e616c54797065223a202275 696e74323536222c20226e616d65223a20226e756d6265724f6646696c6d7322 2c202274797065223a202275696e74323536227d2c7b22696e7465726e616c54 797065223a202275696e74323536222c20226e616d65223a2022756964222c20 2274797065223a202275696e74323536227d5d2c226e616d65223a2022746173 6b222c2274797065223a20227475706c65227d00000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000100 0000000000000000000000000000000000000000000000000000000000000020 00000000000000000000000000000000000000000000000000000000000000a0 0000000000000000000000000000000000000000000000000000000000000060 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000006 0000000000000000000000000000000000000000000000000000000000000003 0000000000000000000000000000000000000000000000000000000000000005 52322d4432000000000000000000000000000000000000000000000000000000', attestation_type: '0x494a736f6e417069000000000000000000000000000000000000000000000000', proof: [ '0xab2384609341b65b4686cf9accd981f8c7f58e47aa41bc49ec60f655c8d99840', '0xc30b0590a4ea59adc7aa5486a9ece81bdcc756ac4d22e09dbe171bf8b50e53b5', '0x4a5fc6d814dd3c52e199396de4d48f7c18274bbf72f9065c1e68306f4fd22c34' ] } ``` The `proof` field is dependent on the round in which the attestation request was submitted; it contains proofs for all of the requests submitted in that round. In the case of a single attestation request it is an empty list `[]` (the proof is the merkle root itself). The decoded `IJsonApi.Response` struct is: ```shell [ attestationType: '0x494a736f6e417069000000000000000000000000000000000000000000000000', sourceId: '0x5745423200000000000000000000000000000000000000000000000000000000', votingRound: '945217', lowestUsedTimestamp: '0', requestBody: [ 'https://swapi.dev/api/people/3/', '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-2] | tonumber)}', '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}', url: 'https://swapi.dev/api/people/3/', postprocessJq: '{name: .name, height: .height, mass: .mass, numberOfFilms: .films | length, uid: (.url | split("/") | .[-2] | tonumber)}', abi_signature: '{"components": [{"internalType": "string", "name": "name", "type": "string"},{"internalType": "uint256", "name": "height", "type": "uint256"},{"internalType": "uint256", "name": "mass", "type": "uint256"},{"internalType": "uint256", "name": "numberOfFilms", "type": "uint256"},{"internalType": "uint256", "name": "uid", "type": "uint256"}],"name": "task","type": "tuple"}' ], responseBody: [ '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000', abi_encoded_data: '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000552322d4432000000000000000000000000000000000000000000000000000000' ] ] ```
## Use the data {/* TODO */} We will now define a simple contract, that will demonstrate how the data can be used onchain. The contract will receive character data from the [Star Wars API](https://swapi.dev/), and store it in a `StarWarsCharacter` struct. It will do so only if the proof is valid. ```solidity title="src/fdcExample/JsonApi.sol" struct StarWarsCharacter { string name; uint256 numberOfMovies; uint256 apiUid; uint256 bmi; } ``` We will also need a `DataTransportObject` struct, that will allow us to decode the data. ```solidity title="src/fdcExample/JsonApi.sol" struct DataTransportObject { string name; uint256 height; uint256 mass; uint256 numberOfMovies; uint256 apiUid; } ``` The code of the contract is as follows. ```solidity title="src/fdcExample/JsonApi.sol" contract StarWarsCharacterList { mapping(uint256 => StarWarsCharacter) public characters; uint256[] public characterIds; function isJsonApiProofValid( IJsonApi.Proof calldata _proof ) private view returns (bool) { // Inline the check for now until we have an official contract deployed return ContractRegistry.auxiliaryGetIJsonApiVerification().verifyJsonApi( _proof ); } function addCharacter(IJsonApi.Proof calldata data) public { require(isJsonApiProofValid(data), "Invalid proof"); DataTransportObject memory dto = abi.decode( data.data.responseBody.abi_encoded_data, (DataTransportObject) ); require(characters[dto.apiUid].apiUid == 0, "Character already exists"); StarWarsCharacter memory character = StarWarsCharacter({ name: dto.name, numberOfMovies: dto.numberOfMovies, apiUid: dto.apiUid, bmi: (dto.mass * 100 * 100) / (dto.height * dto.height) }); characters[dto.apiUid] = character; characterIds.push(dto.apiUid); } function getAllCharacters() public view returns (StarWarsCharacter[] memory) { StarWarsCharacter[] memory result = new StarWarsCharacter[]( characterIds.length ); for (uint256 i = 0; i < characterIds.length; i++) { result[i] = characters[characterIds[i]]; } return result; } } ``` ### Verify proof FDC optimizes onchain storage costs by implementing a hybrid data verification system. Instead of storing complete datasets onchain, it stores only Merkle proofs, while maintaining the actual data through trusted offchain providers. This approach significantly reduces gas costs while preserving data integrity. When requested, data providers supply the original data along with its corresponding Merkle proof. The protocol verifies data authenticity by comparing the provided Merkle proof against the onchain Merkle root. A successful match confirms the data's integrity and authenticity within the FDC system. While data verification is optional if you trust your data provider, FDC ensures transparency by making verification possible at any time. This capability is crucial for maintaining system integrity and allowing users to independently verify data when needed, particularly in production environments. FDC provides verification functionality through the `FdcVerification` contract. If we proof is valid, the function `verifyEVMTransaction` will return `true`, otherwise `false`. We deploy and verify this contract with the `deployAndVerifyContract` function in the `scripts/fdcExample/JsonApi.ts` file. ```typescript title="scripts/fdcExample/JsonApi.ts" async function deployAndVerifyContract() { const args: any[] = []; const characterList: StarWarsCharacterListInstance = await StarWarsCharacterList.new(...args); try { await run("verify:verify", { address: characterList.address, constructorArguments: args, }); } catch (e: any) { console.log(e); } console.log("StarWarsCharacterList deployed to", characterList.address, "\n"); return characterList; } ``` ## Interact with contract We define an additional function that allows us to interact with the just deployed contract. The `interactWithContract` function also takes the proof retrieved in the previous step as an argument. It abi decodes the `response_hex` value to an `IJsonApi.Response` struct. From that and the array of proofs, it constructs an `IJsonApi.Proof` object, on which it call the `registerAddress` function of the `AddressRegistry` contract deployed above. The contract verifies the address, and the script prints it to the console. ```typescript title="scripts/fdcExample/JsonApi.ts" async function interactWithContract( characterList: StarWarsCharacterListInstance, proof: any, ) { console.log("Proof hex:", proof.response_hex, "\n"); // A piece of black magic that allows us to read the response type from an artifact const IJsonApiVerification = await artifacts.require("IJsonApiVerification"); const responseType = IJsonApiVerification._json.abi[0].inputs[0].components[1]; console.log("Response type:", responseType, "\n"); const decodedResponse = web3.eth.abi.decodeParameter( responseType, proof.response_hex, ); console.log("Decoded proof:", decodedResponse, "\n"); const transaction = await characterList.addCharacter({ merkleProof: proof.proof, data: decodedResponse, }); console.log("Transaction:", transaction.tx, "\n"); console.log( "Star Wars Characters:\n", await characterList.getAllCharacters(), "\n", ); } ``` We can run the whole script by calling the following console command. ```bash yarn hardhat run scripts/fdcExample/JsonApi.ts ``` --- ## Verify Payment Nonexistence ## Reference Payment Nonexistence [Full specification on GitHub](https://github.com/flare-foundation/songbird-state-connector-protocol/blob/main/specs/attestations/active-types/ReferencedPaymentNonexistence.md). You are getting more and more familiar with the attestation types, and you are starting to see that they are very powerful and can be used in many different ways. Let's check a more involved one—the `ReferencePaymentNonexistence` type. This type is a bit more difficult to implement and properly use, as it requires the attestation client to do a lot of work—they need to prove that a certain payment has not been made. Instead of looking at the transaction and checking if it is valid, you will be looking at the block range and checking that no valid payment conforming to your requirements has been made in the specified block range. ### Type Definition ```solidity title="ReferencedPaymentNonexistence.sol" // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name ReferencedPaymentNonexistence * @custom:id 0x04 * @custom:supported BTC, DOGE, XRP, testBTC, testDOGE, testXRP * @author Flare * @notice Assertion that an agreed-upon payment has not been made by a certain deadline. * A confirmed request shows that a transaction meeting certain criteria (address, amount, reference) did not appear in the specified block range. * * * This type of attestation can be used to e.g. provide grounds to liquidate funds locked by a smart contract on Flare when a payment is missed. * * @custom:verification If `firstOverflowBlock` cannot be determined or does not have a sufficient [number of confirmations](/specs/attestations/configs.md#finalityconfirmation), the attestation request is rejected. * If `firstOverflowBlockNumber` is higher or equal to `minimalBlockNumber`, the request is rejected. * The search range are blocks between heights including `minimalBlockNumber` and excluding `firstOverflowBlockNumber`. * If the verifier does not have a view of all blocks from `minimalBlockNumber` to `firstOverflowBlockNumber`, the attestation request is rejected. * * The request is confirmed if no transaction meeting the specified criteria is found in the search range. * The criteria and timestamp are chain specific. * ### UTXO (Bitcoin and Dogecoin) * * * Criteria for the transaction: * * * - It is not coinbase transaction. * - The transaction has the specified [standardPaymentReference](/specs/attestations/external-chains/standardPaymentReference.md#btc-and-doge-blockchains). * - The sum of values of all outputs with the specified address minus the sum of values of all inputs with the specified address is greater than `amount` (in practice the sum of all values of the inputs with the specified address is zero). * * * Timestamp is `mediantime`. * ### XRPL * * * * Criteria for the transaction: * - The transaction is of type payment. * - The transaction has the specified [standardPaymentReference](/specs/attestations/external-chains/standardPaymentReference.md#xrp), * - One of the following is true: * - Transaction status is `SUCCESS` and the amount received by the specified destination address is greater than the specified `value`. * - Transaction status is `RECEIVER_FAILURE` and the specified destination address would receive an amount greater than the specified `value` had the transaction been successful. * * * Timestamp is `close_time` converted to UNIX time. * * @custom:lut `minimalBlockTimestamp` */ interface ReferencedPaymentNonexistence { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response as defined. * @param requestBody Data defining the request. Type (struct) and interpretation is determined by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the Data Connector round in which the request was considered. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction of the response body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for ReferencePaymentNonexistence attestation type * @param minimalBlockNumber The start block of the search range. * @param deadlineBlockNumber The blockNumber to be included in the search range. * @param deadlineTimestamp The timestamp to be included in the search range. * @param destinationAddressHash The standard address hash of the address to which the payment had to be done. * @param amount The requested amount in minimal units that had to be paid. * @param standardPaymentReference The requested standard payment reference. * @custom:below The `standardPaymentReference` should not be zero (as a 32-byte sequence). */ struct RequestBody { uint64 minimalBlockNumber; uint64 deadlineBlockNumber; uint64 deadlineTimestamp; bytes32 destinationAddressHash; uint256 amount; bytes32 standardPaymentReference; } /** * @notice Response body for ReferencePaymentNonexistence attestation type. * @param minimalBlockTimestamp The timestamp of the minimalBlock. * @param firstOverflowBlockNumber The height of the firstOverflowBlock. * @param firstOverflowBlockTimestamp The timestamp of the firstOverflowBlock. * @custom:below `firstOverflowBlock` is the first block that has block number higher than `deadlineBlockNumber` and timestamp later than `deadlineTimestamp`. * The specified search range are blocks between heights including `minimalBlockNumber` and excluding `firstOverflowBlockNumber`. */ struct ResponseBody { uint64 minimalBlockTimestamp; uint64 firstOverflowBlockNumber; uint64 firstOverflowBlockTimestamp; } } ``` #### Request Body The request body for the `ReferencePaymentNonexistence` attestation type is a bit larger, as you need to specify the range of blocks to check and the criteria for the payment to check. - **`minimalBlockNumber`**: The start block of the search range. - **`deadlineBlockNumber`**: The block number to be included in the search range. - **`deadlineTimestamp`**: The timestamp to be included in the search range. By including both block number and timestamp, the requested range will encompass all blocks from `minimalBlockNumber` to `deadlineBlockNumber` and all blocks with timestamps from `minimalBlockTimestamp` to `deadlineTimestamp`. - **`destinationAddressHash`**: The standard address hash of the address to which the payment should have been made. - **`amount`**: The requested amount in minimal units that should have been paid. The amount is chain specific. - **`standardPaymentReference`**: The requested standard payment reference that the payment should have had. #### Response Body The response body is simpler and essentially contains the searched range: - **`minimalBlockTimestamp`**: The timestamp of the minimal block that was included in the search range—this is the timestamp of the block with `minimalBlockNumber`. - **`firstOverflowBlockNumber`**: The height of the first overflow block. This is the first block with a block number higher than `deadlineBlockNumber` and a timestamp later than `deadlineTimestamp`. - **`firstOverflowBlockTimestamp`**: The timestamp of the first overflow block. This is the timestamp of the first block with a block number higher than `deadlineBlockNumber` and a timestamp later than `deadlineTimestamp`. #### Confirmation If the request is confirmed, it means that there was no payment in the specified range (including the minimal block, but excluding the maximal block) with an amount greater than or equal to the requested amount and with the requested reference. The full rules for verification are quite complex (and chain-dependent) and are available in the [specification](https://github.com/flare-foundation/songbird-state-connector-protocol/blob/main/specs/attestations/active-types/ReferencedPaymentNonexistence.md#verification). The important point is that the request is confirmed if no transaction meeting the specified criteria is found in the search range. ### Example To produce a correct and thorough example that allows you to test everything properly, you need to be careful. Since you are proving a negative, any mistake during request preparation can result in a transaction that was not made (a simple mis-encoding of a memo field would almost certainly produce a non-existing transaction) and give a false sense of security. To ensure accuracy, structure your request as follows: 1. **Create a transaction** with a reference payment and some nonzero value. 2. **Confirm `Payment` attestation request** to make sure you get back the correct reference and value, ensuring the transaction is seen. Use the information about when this transaction happened to construct a range for the next step, ensuring it contains your transaction. 3. **Make three requests for non-existing payments**: - **Correct (or lower) value and correct reference**: This should return `INVALID`, as the verifier can't prove the non-existence of such a transaction. - **Correct value but slightly wrong payment reference**: Change just one index of the reference. This should be confirmed, as no such transaction exists (the payment reference does not match). - **Too large value but correct payment reference**: This should be confirmed, as the transaction with the payment reference exists but does not transfer enough value. #### XRP Ledger The example code that showcases this on testnet XRP Ledger is available in `tryXRPLPaymentNonExistence.ts`. ```typescript title="tryXRPLPaymentNonExistence.ts" const xrpl = require("xrpl"); const { XRPL_PRIVATE_KEY, ATTESTATION_URL, ATTESTATION_API_KEY } = process.env; const receiverAddress = "r9RLXvWuRro3RX33pk4xsN58tefYZ8Tvbj"; function toHex(data: string): string { var result = ""; for (var i = 0; i < data.length; i++) { result += data.charCodeAt(i).toString(16); } return "0x" + result.padEnd(64, "0"); } function fromHex(data: string): string { data = data.replace(/^(0x\.)/, ""); return data .split(/(\w\w)/g) .filter((p) => !!p) .map((c) => String.fromCharCode(parseInt(c, 16))) .join(""); } async function prepareAttestationResponse( attestationType: string, network: string, sourceId: string, requestBody: any, ): Promise { const response = await fetch( `${ATTESTATION_URL}/verifier/${network}/${attestationType}/prepareResponse`, { method: "POST", headers: { "X-API-KEY": ATTESTATION_API_KEY as string, "Content-Type": "application/json", }, body: JSON.stringify({ attestationType: toHex(attestationType), sourceId: toHex(sourceId), requestBody: requestBody, }), }, ); const data = await response.json(); return data; } async function getXRPLclient(): Promise { const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233"); await client.connect(); return client; } async function sendXRPLTransaction( message: string = "", amount: number = 10, target: string = "r9RLXvWuRro3RX33pk4xsN58tefYZ8Tvbj", ): Promise { const client = await getXRPLclient(); const test_wallet = xrpl.Wallet.fromSeed(XRPL_PRIVATE_KEY); // Standard payment reference must be 32 bytes - so we right pad with 0 const MemoData = xrpl.convertStringToHex(message).padEnd(64, "0"); const MemoType = xrpl.convertStringToHex("Text"); const MemoFormat = xrpl.convertStringToHex("text/plain"); let memos = []; if (message) { memos.push({ Memo: { MemoType: MemoType, MemoData: MemoData, MemoFormat: MemoFormat, }, }); } const transaction = await client.autofill({ TransactionType: "Payment", Account: test_wallet.address, Amount: amount.toString(), Destination: target, Memos: memos, }); const signed = test_wallet.sign(transaction); console.log( `See transaction at https://testnet.xrpl.org/transactions/${signed.hash}`, ); await client.submitAndWait(signed.tx_blob); await client.disconnect(); // sleep for 10 seconds to allow the transaction to be processed await new Promise((resolve) => setTimeout(resolve, 10 * 1000)); console.log("Payment:"); // 1. prove the payment: const resultPayment = await prepareAttestationResponse( "Payment", "xrp", "testXRP", { transactionId: "0x" + signed.hash, inUtxo: "0", utxo: "0", }, ); if (resultPayment.status != "VALID") { console.log("Something wrong when confirming payment"); } console.log(resultPayment); if ( resultPayment.response.responseBody.standardPaymentReference != "0x" + MemoData ) { console.log("Something wrong with message reference"); console.log(resultPayment.response.responseBody.standardPaymentReference); console.log(MemoData); } if ( resultPayment.response.responseBody.receivingAddressHash != web3.utils.soliditySha3(target) ) { console.log("Something wrong with target address hash"); } // Get information about transaction: block and block timestamp -> we will need this to create the range, where the transaction has happened console.log("Failing non existence proof:"); const blockNumber = Number(resultPayment.response.responseBody.blockNumber); const blockTimestamp = Number( resultPayment.response.responseBody.blockTimestamp, ); const targetRange = { minimalBlockNumber: (blockNumber - 5).toString(), // Search few block before deadlineBlockNumber: (blockNumber + 1).toString(), // Search a few blocks after, but not too much, as they need to already be indexed by attestation clients deadlineTimestamp: (blockTimestamp + 3).toString(), // Search a bit after destinationAddressHash: web3.utils.soliditySha3(target), // The target address for transaction }; // Try to verify non existence for a transaction and correct parameters // This should not verify it const resultFailedNonExistence = await prepareAttestationResponse( "ReferencedPaymentNonexistence", "xrp", "testXRP", { ...targetRange, amount: amount.toString(), standardPaymentReference: "0x" + MemoData, }, ); console.log(resultFailedNonExistence); if (resultFailedNonExistence.status != "INVALID") { console.log("Something wrong with failed non existence"); } console.log("Successful non existence proofs:"); // Change the memo field a bit and successfully prove non existence let wrongMemoData = xrpl.convertStringToHex(message).padEnd(64, "1"); // We pad 1 instead of 0 const resultWrongMemoNonExistence = await prepareAttestationResponse( "ReferencedPaymentNonexistence", "xrp", "testXRP", { ...targetRange, amount: amount.toString(), standardPaymentReference: "0x" + wrongMemoData, }, ); console.log(resultWrongMemoNonExistence); if (resultWrongMemoNonExistence.status != "VALID") { console.log("Something wrong with wrong memo non existence"); } // Change the value and successfully prove non existence. const resultWrongAmountNonExistence = await prepareAttestationResponse( "ReferencedPaymentNonexistence", "xrp", "testXRP", { ...targetRange, amount: (amount + 1).toString(), // Increase the amount, so the transaction we made is now invalid standardPaymentReference: "0x" + MemoData, }, ); console.log(resultWrongAmountNonExistence); if (resultWrongAmountNonExistence.status != "VALID") { console.log("Something wrong with wrong amount non existence"); } } async function main() { await sendXRPLTransaction("Hello world!"); } main().then(() => process.exit(0)); ``` Keep in mind, that the requested range can be quite large, so the verifiers might not be able to confirm the response (as they might not have the view of all blocks from `minimalBlockNumber` to `firstOverflowBlockNumber`), so the request might be rejected. ```json // See transaction at https://testnet.xrpl.org/transactions/C2B493B8AE2E3C105D004D8AFBB4AFB5CA758608504CCE895C9331291DA19D75 // Payment: { status: 'VALID', response: { attestationType: '0x5061796d656e7400000000000000000000000000000000000000000000000000', sourceId: '0x7465737458525000000000000000000000000000000000000000000000000000', votingRound: '0', lowestUsedTimestamp: '1708830051', requestBody: { transactionId: '0xC2B493B8AE2E3C105D004D8AFBB4AFB5CA758608504CCE895C9331291DA19D75', inUtxo: '0', utxo: '0' }, responseBody: { blockNumber: '45680731', blockTimestamp: '1708830051', sourceAddressHash: '0xa1ca3089c3e9f4c6e9ccf2bfb65bcf3e9d7544a092c79d642d5d34a54e0267e1', receivingAddressHash: '0x0555194538763da400394fc7184432e9a006565fa710392ea1a86486eb83920f', intendedReceivingAddressHash: '0x0555194538763da400394fc7184432e9a006565fa710392ea1a86486eb83920f', standardPaymentReference: '0x48656C6C6F20776F726C64210000000000000000000000000000000000000000', spentAmount: '22', intendedSpentAmount: '22', receivedAmount: '10', intendedReceivedAmount: '10', oneToOne: true, status: '0' } } } Failing non existence proof: { status: 'INVALID' } Successful non existence proofs: { status: 'VALID', response: { attestationType: '0x5265666572656e6365645061796d656e744e6f6e6578697374656e6365000000', sourceId: '0x7465737458525000000000000000000000000000000000000000000000000000', votingRound: '0', lowestUsedTimestamp: '1708830033', requestBody: { minimalBlockNumber: '45680726', deadlineBlockNumber: '45680732', deadlineTimestamp: '1708830054', destinationAddressHash: '0x0555194538763da400394fc7184432e9a006565fa710392ea1a86486eb83920f', amount: '10', standardPaymentReference: '0x48656C6C6F20776F726C64211111111111111111111111111111111111111111' }, responseBody: { minimalBlockTimestamp: '45680726', firstOverflowBlockNumber: '45680733', firstOverflowBlockTimestamp: '1708830060' } } } { status: 'VALID', response: { attestationType: '0x5265666572656e6365645061796d656e744e6f6e6578697374656e6365000000', sourceId: '0x7465737458525000000000000000000000000000000000000000000000000000', votingRound: '0', lowestUsedTimestamp: '1708830033', requestBody: { minimalBlockNumber: '45680726', deadlineBlockNumber: '45680732', deadlineTimestamp: '1708830054', destinationAddressHash: '0x0555194538763da400394fc7184432e9a006565fa710392ea1a86486eb83920f', amount: '11', standardPaymentReference: '0x48656C6C6F20776F726C64210000000000000000000000000000000000000000' }, responseBody: { minimalBlockTimestamp: '45680726', firstOverflowBlockNumber: '45680733', firstOverflowBlockTimestamp: '1708830060' } } } ``` --- ## URL Parsing Security # URL Parsing Security for FDC When using the `Web2Json` attestation type, your smart contract receives data along with a proof that includes the source URL. Without proper URL validation, an attacker could submit a valid FDC proof from a malicious source and have your contract accept it as legitimate data. This guide covers security patterns for parsing and validating URLs in your FDC-enabled smart contracts. ## The Problem: Man-in-the-Middle Attacks FDC proves that data came from a specific URL. It does **not** prove that the URL is the one you intended to use. Consider a price feed contract that expects data from CoinGecko. An attacker could: 1. Create a malicious API endpoint that returns manipulated prices 2. Get a valid FDC attestation for their malicious endpoint 3. Submit this proof to your contract 4. Your contract accepts the manipulated data because the FDC proof is cryptographically valid URL validation ensures your contract only accepts data from trusted sources. ## Security Layers A robust URL validation strategy includes multiple layers: | Layer | Purpose | Example | | ---------- | --------------------------------------------- | ------------------------------- | | Protocol | Prevent MitM on the wire | HTTPS only | | Host | Restrict to trusted domains | `api.coingecko.com` | | Path | Validate the specific endpoint | `/api/v3/coins/bitcoin/history` | | Parameters | Verify query parameters match expected values | Asset ID extraction | ## Implementation Patterns ### Pattern 1: HTTPS Enforcement Always require HTTPS to prevent network-level interception. ```solidity title="contracts/UrlSecurity.sol" function _validateProtocol(string memory _url) internal pure { bytes memory urlBytes = bytes(_url); bytes memory httpsPrefix = bytes("https://"); if (!_startsWith(urlBytes, httpsPrefix)) { revert InvalidUrlProtocol(); } } function _startsWith(bytes memory data, bytes memory prefix) internal pure returns (bool) { uint256 prefixLen = prefix.length; if (data.length < prefixLen) return false; for (uint256 i = 0; i < prefixLen; ) { if (data[i] != prefix[i]) return false; unchecked { ++i; } } return true; } ``` ### Pattern 2: Host Extraction and Validation Extract the host from the URL and validate against a whitelist. ```solidity title="contracts/UrlSecurity.sol" function _extractHost(string memory _url) internal pure returns (string memory) { bytes memory urlBytes = bytes(_url); bytes memory httpsPrefix = bytes("https://"); uint256 startIndex = httpsPrefix.length; uint256 urlLen = urlBytes.length; uint256 endIndex = urlLen; // Find the first "/" after the host for (uint256 i = startIndex; i < urlLen; ) { if (urlBytes[i] == "/") { endIndex = i; break; } unchecked { ++i; } } return string(_slice(urlBytes, startIndex, endIndex)); } function _validateHost(string memory _url) internal pure { string memory host = _extractHost(_url); string memory lowerHost = _toLowerCase(host); bool validHost = _stringsEqual(lowerHost, "api.coingecko.com") || _stringsEqual(lowerHost, "api.trusted-source.com"); if (!validHost) { revert InvalidUrlHost(_url, host); } } ``` Case-insensitive comparison prevents bypasses using mixed-case hostnames. ```solidity title="contracts/UrlSecurity.sol" function _toLowerCase(string memory str) internal pure returns (string memory) { bytes memory strBytes = bytes(str); uint256 len = strBytes.length; bytes memory result = new bytes(len); for (uint256 i = 0; i < len; ) { bytes1 char = strBytes[i]; // Convert A-Z (65-90) to a-z (97-122) if (char >= 0x41 && char <= 0x5A) { result[i] = bytes1(uint8(char) + 32); } else { result[i] = char; } unchecked { ++i; } } return string(result); } ``` ### Pattern 3: Path Validation with StartsWith Validate that the path begins with the expected endpoint. Using `startsWith` prevents prefix injection attacks where an attacker creates a path like `/malicious/api/v1/data` that contains your expected path as a substring. ```solidity title="contracts/UrlSecurity.sol" function _extractPath(string memory _url) internal pure returns (string memory) { bytes memory urlBytes = bytes(_url); bytes memory httpsPrefix = bytes("https://"); uint256 startIndex = httpsPrefix.length; uint256 urlLen = urlBytes.length; // Find the first "/" after the host (start of path) for (uint256 i = startIndex; i < urlLen; ) { if (urlBytes[i] == "/") { return string(_slice(urlBytes, i, urlLen)); } unchecked { ++i; } } return ""; } function _validatePath(string memory _url, string memory expectedPathPrefix) internal pure { string memory path = _extractPath(_url); if (!_startsWith(bytes(path), bytes(expectedPathPrefix))) { revert InvalidUrlPath(_url, path); } } ``` ### Pattern 4: Parameter Extraction For APIs like CoinGecko where the asset identifier is embedded in the URL path, extract and validate it. ```solidity title="contracts/UrlSecurity.sol" /** * @notice Extracts the asset ID from a URL like "/coins/{id}/history..." * @param _url The full URL string * @return The extracted asset ID */ function _extractAssetIdFromUrl(string memory _url) internal pure returns (string memory) { bytes memory urlBytes = bytes(_url); bytes memory prefix = bytes("/coins/"); bytes memory suffix = bytes("/history"); uint256 startIndex = _indexOf(urlBytes, prefix); if (startIndex == type(uint256).max) { return ""; // Prefix not found } startIndex += prefix.length; uint256 endIndex = _indexOfFrom(urlBytes, suffix, startIndex); if (endIndex == type(uint256).max) { endIndex = urlBytes.length; } return string(_slice(urlBytes, startIndex, endIndex)); } function _indexOf(bytes memory data, bytes memory marker) internal pure returns (uint256) { uint256 dataLen = data.length; uint256 markerLen = marker.length; if (markerLen == 0 || dataLen < markerLen) return type(uint256).max; for (uint256 i = 0; i <= dataLen - markerLen; i++) { bool found = true; for (uint256 j = 0; j < markerLen; j++) { if (data[i + j] != marker[j]) { found = false; break; } } if (found) return i; } return type(uint256).max; } function _indexOfFrom(bytes memory data, bytes memory marker, uint256 from) internal pure returns (uint256) { uint256 dataLen = data.length; uint256 markerLen = marker.length; if (markerLen == 0 || dataLen < markerLen) return type(uint256).max; for (uint256 i = from; i <= dataLen - markerLen; i++) { bool found = true; for (uint256 j = 0; j < markerLen; j++) { if (data[i + j] != marker[j]) { found = false; break; } } if (found) return i; } return type(uint256).max; } ``` Then validate the extracted ID matches your expected asset: ```solidity title="contracts/UrlSecurity.sol" function verifyPrice(IWeb2Json.Proof calldata _proof) external { // Extract and validate the asset ID from the URL string memory extractedId = _extractAssetIdFromUrl(_proof.data.requestBody.url); string memory expectedId = assetIdMapping[expectedSymbol]; if (keccak256(bytes(extractedId)) != keccak256(bytes(expectedId))) { revert InvalidAssetIdInUrl(_proof.data.requestBody.url, extractedId, expectedId); } // Continue with FDC verification... require( ContractRegistry.getFdcVerification().verifyWeb2Json(_proof), "Invalid proof" ); } ``` ## Complete Example Here is a complete URL validation function combining all security layers: ```solidity title="contracts/SecureFdcConsumer.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract SecureFdcConsumer { // Allowed hosts for data sources string[] public allowedHosts; // Expected API path prefix string public expectedPathPrefix; error InvalidUrlProtocol(); error InvalidUrlHost(string url, string host); error InvalidUrlPath(string url, string path); error SliceOutOfBounds(); constructor(string[] memory _allowedHosts, string memory _expectedPath) { allowedHosts = _allowedHosts; expectedPathPrefix = _expectedPath; } function _validateUrl(string memory _url) internal view { bytes memory urlBytes = bytes(_url); // 1. Enforce HTTPS if (!_startsWith(urlBytes, bytes("https://"))) { revert InvalidUrlProtocol(); } // 2. Validate host string memory host = _extractHost(_url); string memory lowerHost = _toLowerCase(host); bool validHost = false; for (uint256 i = 0; i < allowedHosts.length; i++) { if (_stringsEqual(lowerHost, allowedHosts[i])) { validHost = true; break; } } if (!validHost) { revert InvalidUrlHost(_url, host); } // 3. Validate path string memory path = _extractPath(_url); if (!_startsWith(bytes(path), bytes(expectedPathPrefix))) { revert InvalidUrlPath(_url, path); } } function consumeData(IWeb2Json.Proof calldata _proof) external { // Validate URL before accepting proof _validateUrl(_proof.data.requestBody.url); // Verify FDC proof require( ContractRegistry.getFdcVerification().verifyWeb2Json(_proof), "Invalid proof" ); // Process the verified data... } // Helper functions (see implementations above) function _startsWith(bytes memory data, bytes memory prefix) internal pure returns (bool) { /* ... */ } function _extractHost(string memory _url) internal pure returns (string memory) { /* ... */ } function _extractPath(string memory _url) internal pure returns (string memory) { /* ... */ } function _toLowerCase(string memory str) internal pure returns (string memory) { /* ... */ } function _stringsEqual(string memory a, string memory b) internal pure returns (bool) { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); } function _slice(bytes memory data, uint256 start, uint256 end) internal pure returns (bytes memory) { /* ... */ } } ``` ## Common Vulnerabilities ### Missing HTTPS Check Without HTTPS enforcement, an attacker could intercept HTTP requests and modify responses. ```solidity // ❌ VULNERABLE: Accepts HTTP URLs function validateUrl(string memory url) internal pure { // Only checks host, not protocol require(_isAllowedHost(url), "Invalid host"); } // ✅ SECURE: Requires HTTPS function validateUrl(string memory url) internal pure { require(_startsWith(bytes(url), bytes("https://")), "HTTPS required"); require(_isAllowedHost(url), "Invalid host"); } ``` ### Case-Sensitive Host Comparison DNS is case-insensitive, so `API.COINGECKO.COM` resolves to the same server as `api.coingecko.com`. ```solidity // ❌ VULNERABLE: Case-sensitive comparison function isValidHost(string memory host) internal pure returns (bool) { return keccak256(bytes(host)) == keccak256(bytes("api.coingecko.com")); } // ✅ SECURE: Case-insensitive comparison function isValidHost(string memory host) internal pure returns (bool) { return keccak256(bytes(_toLowerCase(host))) == keccak256(bytes("api.coingecko.com")); } ``` ### Contains Instead of StartsWith Using `contains` for path validation allows prefix injection attacks. ```solidity // ❌ VULNERABLE: Contains check allows prefix injection // Attacker could use: /malicious/api/v1/price -> contains "/api/v1/price" = true function isValidPath(string memory path) internal pure returns (bool) { return _contains(bytes(path), bytes("/api/v1/price")); } // ✅ SECURE: StartsWith prevents prefix injection function isValidPath(string memory path) internal pure returns (bool) { return _startsWith(bytes(path), bytes("/api/v1/price")); } ``` ## Gas Considerations URL parsing in Solidity is gas-intensive due to string operations. Consider these optimizations: 1. **Cache host comparisons**: Pre-compute keccak256 hashes of allowed hosts 2. **Limit URL length**: Reject URLs exceeding a maximum length before parsing 3. **Use assembly**: For production contracts, consider assembly-optimized string operations ```solidity title="contracts/GasOptimized.sol" // Pre-computed host hashes bytes32 constant COINGECKO_HOST_HASH = keccak256(bytes("api.coingecko.com")); bytes32 constant BACKUP_HOST_HASH = keccak256(bytes("backup.coingecko.com")); function _isValidHost(string memory host) internal pure returns (bool) { bytes32 hostHash = keccak256(bytes(_toLowerCase(host))); return hostHash == COINGECKO_HOST_HASH || hostHash == BACKUP_HOST_HASH; } ``` ## Reference Implementations For production-ready examples, see: - [PriceVerifierCustomFeed](https://github.com/flare-foundation/flare-hardhat-starter/blob/main/contracts/customFeeds/PriceVerifierCustomFeed.sol) - Asset ID extraction from CoinGecko URLs {PriceVerifierCustomFeed} ## Summary Secure URL parsing for FDC requires: 1. **HTTPS enforcement** - Reject HTTP URLs 2. **Host whitelisting** - Only accept data from trusted domains 3. **Case-insensitive comparison** - Prevent bypass via mixed-case hosts 4. **Path validation with startsWith** - Prevent prefix injection attacks 5. **Parameter extraction** - Validate embedded identifiers match expected values Always validate URLs before calling `verifyWeb2Json()`. A valid FDC proof only guarantees the data came from the URL in the proof—it does not guarantee the URL is trustworthy. --- ## IAddressValidity Assert whether a string represents a valid address on an external blockchain. Sourced from `IAddressValidity.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IAddressValidity.sol). ## Overview The IAddressValidity interface enables smart contracts to verify if a given string is a valid address on supported external blockchains. This attestation provides standardized representations of addresses across different chains, facilitating cross-chain operations within the Flare ecosystem. ## Supported Chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet 4), `testDOGE`, `testXRP` | ## Structs ### Request Toplevel request structure. ```solidity struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } ``` #### Parameters - `attestationType`: ID of the attestation type (0x05 for AddressValidity) - `sourceId`: ID of the data source (e.g., BTC, DOGE, XRP) - `messageIntegrityCode`: MessageIntegrityCode derived from the expected response - `requestBody`: Data defining the request ### Response Toplevel response structure. ```solidity struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } ``` #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Toplevel proof structure for verification. ```solidity struct Proof { bytes32[] merkleProof; Response data; } ``` #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody Request body specific to address validity. ```solidity struct RequestBody { string addressStr; } ``` #### Parameters - `addressStr`: Address string to be verified ### ResponseBody Response body specific to address validity. ```solidity struct ResponseBody { bool isValid; string standardAddress; bytes32 standardAddressHash; } ``` #### Parameters - `isValid`: Boolean indicator of the address validity - `standardAddress`: If valid, the standard form of the address; otherwise an empty string - `standardAddressHash`: If valid, the standard address hash; otherwise a zero bytes32 string ## Chain-Specific Address Formats ### Bitcoin (BTC) - Supports P2PKH, P2SH, P2WPKH, P2WSH, and P2TR addresses - Bech32 and Bech32m encoding for SegWit addresses - Legacy addresses with Base58Check encoding ### Dogecoin (DOGE) - Supports P2PKH and P2SH addresses - Base58Check encoding - Different address prefixes than Bitcoin ### XRP Ledger (XRPL) - Base58 encoding with checksum - Addresses typically start with "r" - Supports X-addresses for destination tags ## Implementation Notes - The attestation ID for AddressValidity is `0x05` - The `lowestUsedTimestamp` value is `0xffffffffffffffff` (maximum 64-bit value) - For invalid addresses, `standardAddress` will be empty and `standardAddressHash` will be zero - The `standardAddressHash` is computed using `keccak256` on the standardized address string ## Usage Example {AddressVerifier} Open example in Remix --- ## IBalanceDecreasingTransaction An interface to detect transactions that decrease the balance of a specific address or are signed by that address on external blockchains. Sourced from `IBalanceDecreasingTransaction.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IBalanceDecreasingTransaction.sol). ## Overview The IBalanceDecreasingTransaction interface allows smart contracts to identify and verify transactions on external blockchains that either: 1. Decrease the balance of a specific address, or 2. Are signed by the address in question (even if the balance increases) This attestation type is particularly useful for detecting when a party has violated an agreement by moving funds that were promised to be locked, providing grounds for liquidating collateral locked in a smart contract on Flare. ## Supported Chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet 4), `testDOGE`, `testXRP` | ## Chain-Specific Implementation Details ### UTXO Chains (Bitcoin and Dogecoin) - **sourceAddressIndicator**: Index of the transaction input in hex padded to a 0x-prefixed 32-byte string - **sourceAddress**: Address of the indicated transaction input - **spentAmount**: Sum of values of all inputs with sourceAddress minus sum of all outputs with sourceAddress (can be negative) - **blockTimestamp**: Mediantime of the block ### Account-based Chains (XRPL) - **sourceAddressIndicator**: Standard address hash of the address to monitor - **sourceAddress**: Address whose standard hash matches the sourceAddressIndicator - **spentAmount**: Difference between the balance after and before the transaction (can be negative) - **blockTimestamp**: close_time of the ledger converted to Unix time ## Interface Definition ```solidity // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name IBalanceDecreasingTransaction * @custom:id 0x02 * @custom:supported BTC, DOGE, XRP * @author Flare * @notice A detection of a transaction that either decreases the balance for some address or is * signed by the source address. * Such an attestation could prove a violation of an agreement and therefore provides grounds to liquidate * some funds locked by a smart contract on Flare. * * A transaction is considered "balance decreasing" for the address, if the balance after the * transaction is lower than before or the address is among the signers of the transaction * (even if its balance is greater than before the transaction). * @custom:verification The transaction with `transactionId` is fetched from the API of the * source blockchain node or relevant indexer. * If the transaction cannot be fetched or the transaction is in a block that does not have a * sufficient number of confirmations, the attestation request is rejected. * * Once the transaction is received, the response fields are extracted if the transaction is balance * decreasing for the indicated address. * Some of the request and response fields are chain specific as described below. * The fields can be computed with the help of a balance decreasing summary. * * ### UTXO (Bitcoin and Dogecoin) * * - `sourceAddressIndicator` is the the index of the transaction input in hex padded to a 0x prefixed 32-byte string. * If the indicated input does not exist or the indicated input does not have the address, * the attestation request is rejected. * The `sourceAddress` is the address of the indicated transaction input. * - `spentAmount` is the sum of values of all inputs with sourceAddress minus the sum of * all outputs with `sourceAddress`. * Can be negative. * - `blockTimestamp` is the mediantime of a block. * * ### XRPL * * - `sourceAddressIndicator` is the standard address hash of the address whose balance has been decreased. * If the address indicated by `sourceAddressIndicator` is not among the signers of the transaction and the balance * of the address was not lowered in the transaction, the attestation request is rejected. * * - `spentAmount` is the difference between the balance of the indicated address after and before the transaction. * Can be negative. * - `blockTimestamp` is the close_time of a ledger converted to unix time. * * @custom:lut `blockTimestamp` * @custom:lutlimit `0x127500`, `0x127500`, `0x127500` */ interface IBalanceDecreasingTransaction { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response. * @param requestBody Data defining the request. Type and interpretation is determined by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the FDC round in which the request was considered. * This is a security measure to prevent a collision of attestation hashes. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction of the * response body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for IBalanceDecreasingTransaction attestation type * @param transactionId ID of the payment transaction. * @param sourceAddressIndicator The indicator of the address whose balance has been decreased. */ struct RequestBody { bytes32 transactionId; bytes32 sourceAddressIndicator; } /** * @notice Response body for IBalanceDecreasingTransaction attestation type. * @param blockNumber The number of the block in which the transaction is included. * @param blockTimestamp The timestamp of the block in which the transaction is included. * @param sourceAddressHash Standard address hash of the address indicated by the `sourceAddressIndicator`. * @param spentAmount Amount spent by the source address in minimal units. * @param standardPaymentReference Standard payment reference of the transaction. */ struct ResponseBody { uint64 blockNumber; uint64 blockTimestamp; bytes32 sourceAddressHash; int256 spentAmount; bytes32 standardPaymentReference; } } ``` ## Structs ### Request Toplevel request structure. #### Parameters - `attestationType`: ID of the attestation type (0x02 for BalanceDecreasingTransaction) - `sourceId`: ID of the data source (e.g., BTC, DOGE, XRP) - `messageIntegrityCode`: MessageIntegrityCode derived from the expected response - `requestBody`: Data defining the request ### Response Toplevel response structure. #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Toplevel proof structure for verification. #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody Request body specific to balance decreasing transactions. #### Parameters - `transactionId`: Unique identifier of the transaction to check - `sourceAddressIndicator`: Indicator of the address to monitor (interpretation varies by chain) ### ResponseBody Response body containing details about the balance decreasing transaction. #### Parameters - `blockNumber`: Block number containing the transaction - `blockTimestamp`: Timestamp of the block containing the transaction - `sourceAddressHash`: Standard hash of the address indicated by sourceAddressIndicator - `spentAmount`: Amount spent by the source address (can be negative) - `standardPaymentReference`: Standard payment reference of the transaction ## Implementation Notes - Attestation ID: `0x02` - The `lowestUsedTimestamp` parameter uses the value of `blockTimestamp` - The `lutlimit` (Lowest Used Timestamp limit) is `0x127500` (1,209,600 seconds = 14 days) for all supported chains - A negative `spentAmount` value indicates the address received more funds than it sent - The transaction is still considered "balance decreasing" if the address signed the transaction, even if the balance increased ## Practical Applications - **Cross-chain Collateral Monitoring**: Detect when funds that should be locked on one chain are moved, triggering liquidation of collateral on Flare - **Agreement Enforcement**: Verify if parties adhere to contractual agreements involving locking funds - **Fraud Detection**: Monitor specific addresses for suspicious outgoing transactions - **Conditional Smart Contracts**: Trigger contract actions based on external chain activity ## Usage Example {LockMonitor} Open example in Remix --- ## IConfirmedBlockHeightExists An interface to verify that a specified block has been confirmed on an external blockchain and to calculate block production rates. Sourced from `IConfirmedBlockHeightExists.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IConfirmedBlockHeightExists.sol). ## Overview The IConfirmedBlockHeightExists interface allows smart contracts to verify that a specific block number exists and has reached the required confirmation threshold on an external blockchain. Additionally, it provides data to calculate block production rates over a specified time window, which can be useful for estimating transaction finality times or implementing time-dependent logic. ## Supported Chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet 4), `testDOGE`, `testXRP` | ## Chain-Specific Confirmation Requirements | Chain | Chain ID | Required Confirmations | Timestamp Source | | ----- | -------- | ---------------------- | ---------------- | | BTC | 0 | 6 | mediantime | | DOGE | 2 | 60 | mediantime | | XRP | 3 | 3 | close_time | ## Interface Definition ```solidity // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name IConfirmedBlockHeightExists * @custom:id 0x02 * @custom:supported BTC, DOGE, XRP * @author Flare * @notice An assertion that a block with `blockNumber` is confirmed. * It also provides data to compute the block production rate in the given time range. * @custom:verification It is checked that the block with `blockNumber` is confirmed by at * least `numberOfConfirmations`. * If it is not, the request is rejected. We note a block on the tip of the chain is confirmed by 1 block. * Then `lowestQueryWindowBlock` is determined and its number and timestamp are extracted. * * * Current confirmation heights consensus: * * * | `Chain` | `chainId` | `numberOfConfirmations` | `timestamp ` | * | ------- | --------- | ----------------------- | ------------ | * | `BTC` | 0 | 6 | mediantime | * | `DOGE` | 2 | 60 | mediantime | * | `XRP` | 3 | 3 | close_time | * * * @custom:lut `lowestQueryWindowBlockTimestamp` * @custom:lutlimit `0x127500`, `0x127500`, `0x127500` */ interface IConfirmedBlockHeightExists { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response as defined. * @param requestBody Data defining the request. Type and interpretation is determined by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the FDC round in which the request was considered. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction of the * response body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for ConfirmedBlockHeightExistsType attestation type * @param blockNumber The number of the block the request wants a confirmation of. * @param queryWindow The length of the period in which the block production rate is to be computed. */ struct RequestBody { uint64 blockNumber; uint64 queryWindow; } /** * @notice Response body for ConfirmedBlockHeightExistsType attestation type * @custom:below `blockNumber`, `lowestQueryWindowBlockNumber`, `blockTimestamp`, `lowestQueryWindowBlockTimestamp` * can be used to compute the average block production time in the specified block range. * @param blockTimestamp The timestamp of the block with `blockNumber`. * @param numberOfConfirmations The depth at which a block is considered confirmed depending on the chain. * All attestation providers must agree on this number. * @param lowestQueryWindowBlockNumber The block number of the latest block that has a timestamp strictly smaller * than `blockTimestamp` - `queryWindow`. * @param lowestQueryWindowBlockTimestamp The timestamp of the block at height `lowestQueryWindowBlockNumber`. */ struct ResponseBody { uint64 blockTimestamp; uint64 numberOfConfirmations; uint64 lowestQueryWindowBlockNumber; uint64 lowestQueryWindowBlockTimestamp; } } ``` ## Structs ### Request Toplevel request structure. #### Parameters - `attestationType`: ID of the attestation type (0x02 for ConfirmedBlockHeightExists) - `sourceId`: ID of the data source (e.g., BTC, DOGE, XRP) - `messageIntegrityCode`: MessageIntegrityCode derived from the expected response - `requestBody`: Data defining the request ### Response Toplevel response structure. #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Toplevel proof structure for verification. #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody Request body specific to block height confirmation. #### Parameters - `blockNumber`: The number of the block to confirm - `queryWindow`: Time period in seconds to calculate block production rate ### ResponseBody Response body specific to block height confirmation. #### Parameters - `blockTimestamp`: Timestamp of the block with `blockNumber` - `numberOfConfirmations`: Required confirmations for the specific chain - `lowestQueryWindowBlockNumber`: Block number of the latest block with timestamp < `blockTimestamp - queryWindow` - `lowestQueryWindowBlockTimestamp`: Timestamp of the block at `lowestQueryWindowBlockNumber` ## Implementation Notes - Attestation ID: `0x02` - Timestamp values vary by chain (mediantime for BTC/DOGE, close_time for XRP) - The `lowestUsedTimestamp` parameter uses the value of `lowestQueryWindowBlockTimestamp` - The `lutlimit` (Lowest Used Timestamp limit) is `0x127500` (1,209,600 seconds = 14 days) for all supported chains ## Calculating Block Production Rate The response fields allow developers to calculate the average block production rate using the formula: ``` blocks_produced = blockNumber - lowestQueryWindowBlockNumber time_elapsed = blockTimestamp - lowestQueryWindowBlockTimestamp average_block_time = time_elapsed / blocks_produced ``` ## Usage Example {BlockchainMonitor} Open example in Remix ## Related Interfaces - [IFdcHub](IFdcHub.md): Primary interface for requesting attestations - [IFdcVerification](IFdcVerification.md): Interface for verifying attestation proofs --- ## IEVMTransaction An interface to relay and verify transactions from EVM-compatible blockchains. Sourced from `IEVMTransaction.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IEVMTransaction.sol). ## Overview The IEVMTransaction interface enables smart contracts on Flare networks to access and verify transaction data from other EVM-compatible blockchains. This attestation type provides access to transaction details, including associated events (logs), allowing cross-chain verification and integration of data from external EVM chains. ## Supported Chains | Network Type | Supported Chains | | ------------ | ---------------------------------------------------------------------------------------------------- | | **Mainnet** | `ETH` (Ethereum), `FLR` (Flare), `SGB` (Songbird) | | **Testnet** | `testETH` (Ethereum Sepolia), `testFLR` (Flare Testnet Coston2), `testSGB` (Songbird Testnet Coston) | ## Interface Definition ```solidity // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name IEVMTransaction * @custom:id 0x06 * @custom:supported ETH, FLR, SGB * @author Flare * @notice A relay of a transaction from an EVM chain. * This type is only relevant for EVM-compatible chains. * @custom:verification If a transaction with the `transactionId` is in a block on the main branch with * at least `requiredConfirmations`, the specified data is relayed. * If an indicated event does not exist, the request is rejected. * @custom:lut `timestamp` * @custom:lutlimit `0x41eb00`, `0x41eb00`, `0x41eb00` */ interface IEVMTransaction { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response. * @param requestBody Data defining the request. Type (struct) and interpretation is * determined by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the FDC round in which the request was considered. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction * of the response body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for EVM transaction attestation type * @custom:below Note that events (logs) are indexed in block not in each transaction. * The contract that uses the attestation should specify the order of event logs as needed and the requestor should * sort `logIndices` with respect to the set specifications. * If possible, the contact should require one `logIndex`. * @param transactionHash Hash of the transaction(transactionHash). * @param requiredConfirmations The height at which a block is considered confirmed by the requestor. * @param provideInput If true, "input" field is included in the response. * @param listEvents If true, events indicated by `logIndices` are included in the response. * Otherwise, no events are included in the response. * @param logIndices If `listEvents` is `false`, this should be an empty list, otherwise, * the request is rejected. If `listEvents` is `true`, this is the list of indices (logIndex) * of the events to be relayed (sorted by the requestor). The array should contain at most 50 indices. * If empty, it indicates all events in order capped by 50. */ struct RequestBody { bytes32 transactionHash; uint16 requiredConfirmations; bool provideInput; bool listEvents; uint32[] logIndices; } /** * @notice Response body for EVM transaction attestation type * @custom:below The fields are in line with transaction provided by EVM node. * @param blockNumber Number of the block in which the transaction is included. * @param timestamp Timestamp of the block in which the transaction is included. * @param sourceAddress The address (from) that signed the transaction. * @param isDeployment Indicate whether it is a contract creation transaction. * @param receivingAddress The address (to) of the receiver of the initial transaction. * Zero address if `isDeployment` is `true`. * @param value The value transferred by the initial transaction in wei. * @param input If `provideInput`, this is the data send along with the initial transaction. * Otherwise it is the default value `0x00`. * @param status Status of the transaction 1 - success, 0 - failure. * @param events If `listEvents` is `true`, an array of the requested events. * Sorted by the logIndex in the same order as `logIndices`. Otherwise, an empty array. */ struct ResponseBody { uint64 blockNumber; uint64 timestamp; address sourceAddress; bool isDeployment; address receivingAddress; uint256 value; bytes input; uint8 status; Event[] events; } /** * @notice Event log record * @custom:above An `Event` is a struct with the following fields: * @custom:below The fields are in line with EVM event logs. * @param logIndex The consecutive number of the event in block. * @param emitterAddress The address of the contract that emitted the event. * @param topics An array of up to four 32-byte strings of indexed log arguments. * @param data Concatenated 32-byte strings of non-indexed log arguments. At least 32 bytes long. * @param removed It is `true` if the log was removed due to a chain reorganization * and `false` if it is a valid log. */ struct Event { uint32 logIndex; address emitterAddress; bytes32[] topics; bytes data; bool removed; } } ``` ## Structs ### Request Toplevel request structure. #### Parameters - `attestationType`: ID of the attestation type (0x06 for EVMTransaction) - `sourceId`: ID of the data source (e.g., ETH, FLR, SGB) - `messageIntegrityCode`: MessageIntegrityCode derived from the expected response - `requestBody`: Data defining the request ### Response Toplevel response structure. #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Toplevel proof structure for verification. #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody Request body specific to EVM transaction verification. #### Parameters - `transactionHash`: The hash of the transaction to relay - `requiredConfirmations`: Number of confirmations required for the block to be considered final - `provideInput`: If true, the transaction input data will be included in the response - `listEvents`: If true, events specified by logIndices will be included in the response - `logIndices`: Array of event indices to be relayed (max 50), if empty and listEvents is true, includes all events up to 50 ### ResponseBody Response body containing transaction details. #### Parameters - `blockNumber`: Block number containing the transaction - `timestamp`: Timestamp of the block containing the transaction - `sourceAddress`: Address that signed the transaction (from) - `isDeployment`: True if the transaction created a contract - `receivingAddress`: Address receiving the transaction (to), zero address if isDeployment is true - `value`: Value transferred in wei - `input`: Transaction input data if provideInput is true, otherwise "0x00" - `status`: Transaction status (1 = success, 0 = failure) - `events`: Array of requested events if listEvents is true, otherwise empty ### Event Structure representing a transaction event (log). #### Parameters - `logIndex`: Index of the event within the block - `emitterAddress`: Contract address that emitted the event - `topics`: Array of indexed event parameters (up to 4) - `data`: Non-indexed event parameters, concatenated as 32-byte strings - `removed`: True if the event was removed due to a chain reorganization ## Implementation Notes - Attestation ID: `0x06` - The `lowestUsedTimestamp` parameter uses the value of `timestamp` - The `lutlimit` (Lowest Used Timestamp limit) is `0x41eb00` (4,300,800 seconds = ~50 days) for all supported chains - Events are indexed at the block level, not at the transaction level - Maximum of 50 events can be included in a single attestation ## Event Handling When working with events, it's important to remember: 1. Events (logs) are indexed at the **block level**, not at the transaction level 2. The order of events in `logIndices` should follow the requirements of the consuming contract 3. If `listEvents` is set to false, `logIndices` must be an empty array 4. If `listEvents` is true but `logIndices` is empty, all events up to a maximum of 50 will be included 5. Events are returned in the same order as specified in `logIndices` ## Usage Example {EVMTransactionVerifier.toString()} Open example in Remix --- ## IJsonApi :::caution Deprecated The `JsonApi` attestation type is the predecessor of [`Web2Json`](/fdc/reference/IWeb2Json) and is retained in periphery v0.1.41 for backward compatibility only. **New integrations should use `Web2Json`** — it supports the same URL/jq/abi-signature contract plus headers, query parameters, and an explicit HTTP method. The field names below intentionally use the legacy `postprocessJq` / `abi_signature` / `abi_encoded_data` snake_case form. ::: Sourced from `IJsonApi.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IJsonApi.sol). ## Structs ### Request ```solidity struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } ``` ### Response ```solidity struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } ``` ### Proof ```solidity struct Proof { bytes32[] merkleProof; Response data; } ``` ### RequestBody ```solidity struct RequestBody { string url; string postprocessJq; string abi_signature; } ``` ### ResponseBody ```solidity struct ResponseBody { bytes abi_encoded_data; } ``` --- ## IPayment An interface to relay and verify payment transactions in native currencies across multiple external blockchains. Sourced from `IPayment.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IPayment.sol). ## Overview The IPayment interface enables smart contracts on Flare networks to access and verify native currency payment transactions from external blockchains. It provides a standardized way to prove that payments have occurred between entities, similar to traditional banking transfers, with support for payment references. This attestation type handles the different transaction models across various blockchains, offering a unified interface for payment verification. ## Supported Chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet 4), `testDOGE`, `testXRP` | ## Chain-Specific Implementation Details ### UTXO Chains (Bitcoin and Dogecoin) - **Transaction Model**: Uses inputs and outputs, where multiple addresses can contribute inputs and receive outputs - **inUtxo**: Index of the transaction input with the source address - **utxo**: Index of the transaction output with the receiving address - **blockTimestamp**: Derived from the mediantime of the block ### Account-based Chains (XRPL) - **Transaction Model**: Direct transfers between accounts - **inUtxo** and **utxo**: Always set to 0 (not used) - **blockTimestamp**: Derived from the close_time of the ledger converted to UNIX time ## Interface Definition ```solidity // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name IPayment * @custom:id 0x01 * @custom:supported BTC, DOGE, XRP * @author Flare * @notice A relay of a transaction on an external chain that is considered a payment in a native currency. * Various blockchains support different types of native payments. For each blockchain, it is specified how a payment * transaction should be formed to be provable by this attestation type. * The provable payments emulate traditional banking payments from entity A to entity B in native currency with an * optional payment reference. * @custom:verification The transaction with `transactionId` is fetched from the API of the blockchain node or * relevant indexer. * If the transaction cannot be fetched or the transaction is in a block that does not have a sufficient * [number of confirmations](/specs/attestations/configs.md#finalityconfirmation), the attestation request is rejected. * * Once the transaction is received, the payment summary is computed according to the rules for the source chain. * If the summary is successfully calculated, the response is assembled from the summary. * `blockNumber` and `blockTimestamp` are retrieved from the block if they are not included in the transaction data. * For Bitcoin and Dogecoin, `blockTimestamp` is mediantime of the block. * For XRPL, `blockTimestamp` is close time of the ledger converted to UNIX time. * * If the summary is not successfully calculated, the attestation request is rejected. * @custom:lut `blockTimestamp` * @custom:lutlimit `0x127500`, `0x127500`, `0x127500` */ interface IPayment { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response. * @param requestBody Data defining the request. Type (struct) and interpretation is determined * by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the FDC round in which the request was considered. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction * of the response body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for Payment attestation type * @param transactionId ID of the payment transaction. * @param inUtxo For UTXO chains, this is the index of the transaction input with source address. * Always 0 for the non-utxo chains. * @param utxo For UTXO chains, this is the index of the transaction output with receiving address. * Always 0 for the non-utxo chains. */ struct RequestBody { bytes32 transactionId; uint256 inUtxo; uint256 utxo; } /** * @notice Response body for Payment attestation type * @param blockNumber Number of the block in which the transaction is included. * @param blockTimestamp The timestamp of the block in which the transaction is included. * @param sourceAddressHash Standard address hash of the source address. * @param sourceAddressesRoot The root of the Merkle tree of the source addresses. * @param receivingAddressHash Standard address hash of the receiving address. * The zero 32-byte string if there is no receivingAddress (if `status` is not success). * @param intendedReceivingAddressHash Standard address hash of the intended receiving address. * Relevant if the transaction is unsuccessful. * @param spentAmount Amount in minimal units spent by the source address. * @param intendedSpentAmount Amount in minimal units to be spent by the source address. * Relevant if the transaction status is unsuccessful. * @param receivedAmount Amount in minimal units received by the receiving address. * @param intendedReceivedAmount Amount in minimal units intended to be received by the receiving address. * Relevant if the transaction is unsuccessful. * @param standardPaymentReference Standard payment reference of the transaction. * @param oneToOne Indicator whether only one source and one receiver are involved in the transaction. * @param status Success status of the transaction: 0 - success, 1 - failed by sender's fault, * 2 - failed by receiver's fault. */ struct ResponseBody { uint64 blockNumber; uint64 blockTimestamp; bytes32 sourceAddressHash; bytes32 sourceAddressesRoot; bytes32 receivingAddressHash; bytes32 intendedReceivingAddressHash; int256 spentAmount; int256 intendedSpentAmount; int256 receivedAmount; int256 intendedReceivedAmount; bytes32 standardPaymentReference; bool oneToOne; uint8 status; } } ``` ## Structs ### Request Toplevel request structure. #### Parameters - `attestationType`: ID of the attestation type (0x01 for Payment) - `sourceId`: ID of the data source (e.g., BTC, DOGE, XRP) - `messageIntegrityCode`: MessageIntegrityCode derived from the expected response - `requestBody`: Data defining the request ### Response Toplevel response structure. #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Toplevel proof structure for verification. #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody Request body specific to payment verification. #### Parameters - `transactionId`: The unique identifier of the transaction to verify - `inUtxo`: Index of the transaction input with source address (UTXO chains only) - `utxo`: Index of the transaction output with receiving address (UTXO chains only) ### ResponseBody Response body containing payment transaction details. #### Parameters - `blockNumber`: Block number containing the transaction - `blockTimestamp`: Timestamp of the block containing the transaction - `sourceAddressHash`: Standard hash of the source address - `sourceAddressesRoot`: Root of the Merkle tree of all source addresses - `receivingAddressHash`: Standard hash of the receiving address (zero if transaction failed) - `intendedReceivingAddressHash`: Standard hash of the intended receiving address (for failed transactions) - `spentAmount`: Amount spent by the source address in minimal units - `intendedSpentAmount`: Amount intended to be spent (for failed transactions) - `receivedAmount`: Amount received by the receiving address - `intendedReceivedAmount`: Amount intended to be received (for failed transactions) - `standardPaymentReference`: Standard payment reference included in the transaction - `oneToOne`: True if the transaction involves only one source and one receiver - `status`: Transaction status (0=success, 1=sender failure, 2=receiver failure) ## Transaction Status Codes | Status Code | Description | Explanation | | ----------- | ---------------- | ------------------------------------------------------- | | 0 | SUCCESS | Transaction completed successfully | | 1 | SENDER_FAILURE | Transaction failed due to issues on the sender's side | | 2 | RECEIVER_FAILURE | Transaction failed due to issues on the receiver's side | ## Implementation Notes - Attestation ID: `0x01` - The `lowestUsedTimestamp` parameter uses the value of `blockTimestamp` - The `lutlimit` (Lowest Used Timestamp limit) is `0x127500` (1,209,600 seconds = 14 days) for all supported chains - Standard payment references can be included for cross-chain payment identification - For account-based chains like XRPL, `inUtxo` and `utxo` parameters should be set to 0 - The `sourceAddressesRoot` is a Merkle tree root of all transaction input addresses, useful for multi-input transactions - The `oneToOne` flag helps identify simple peer-to-peer transfers versus more complex multi-party transactions ## Standard Payment Reference A standardized payment reference follows a specific format to ensure consistency across different blockchains: - For UTXO chains (BTC, DOGE): Derived from OP_RETURN outputs or specific patterns in transaction data - For XRPL: Derived from the MemoData field in the transaction Payment references allow for correlation of payments across chains and facilitate payment reconciliation in cross-chain applications. ## Usage Example {PaymentVerifier} Open example in Remix --- ## IReferencedPaymentNonexistence An interface to verify that a specific payment, agreed to be completed by a certain deadline, has **not been made** on an external blockchain. Sourced from `IReferencedPaymentNonexistence.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IReferencedPaymentNonexistence.sol). ## Overview The IReferencedPaymentNonexistence interface enables smart contracts on Flare networks to verify the absence of specific payment transactions on external blockchains. This type of attestation is particularly useful for conditional agreements where the absence of a payment by a deadline triggers consequences, such as liquidating collateral or executing penalties in cross-chain financial arrangements. ## Supported Chains | Network Type | Supported Chains | | ------------ | ------------------------------------------------------ | | **Mainnet** | `BTC` (Bitcoin), `DOGE` (Dogecoin), `XRP` (XRP Ledger) | | **Testnet** | `testBTC` (Bitcoin Testnet 4), `testDOGE`, `testXRP` | ## Chain-Specific Verification Criteria ### UTXO Chains (Bitcoin and Dogecoin) A transaction is considered a match (and would invalidate the nonexistence claim) if all of the following are true: - It is **not** a coinbase transaction - The transaction includes the specified standard payment reference - If `checkSourceAddresses` is true, the source addresses root matches the specified one - The sum of outputs to the destination address, minus any inputs from that address, exceeds the specified amount - The transaction appears within the specified block range ### Account-based Chains (XRPL) A transaction is considered a match (and would invalidate the nonexistence claim) if all of the following are true: - The transaction is of type "Payment" - The transaction includes the specified standard payment reference - If `checkSourceAddresses` is true, the source addresses root matches the specified one - One of the following conditions is met: - Transaction status is "SUCCESS" and the amount received by the destination address exceeds the specified amount - Transaction status is "RECEIVER_FAILURE" and the amount that would have been received exceeds the specified amount - The transaction appears within the specified block range ## Interface Definition ```solidity // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name IReferencedPaymentNonexistence * @custom:id 0x04 * @custom:supported BTC, DOGE, XRP * @author Flare * @notice Assertion that an agreed-upon payment has not been made by a certain deadline. * A confirmed request shows that a transaction meeting certain criteria (address, amount, reference) * did not appear in the specified block range. * * * This type of attestation can be used to e.g. provide grounds to liquidate funds locked by a smart * contract on Flare when a payment is missed. * * @custom:verification If `firstOverflowBlock` cannot be determined or does not have a sufficient * number of confirmations, the attestation request is rejected. * If `firstOverflowBlockNumber` is higher or equal to `minimalBlockNumber`, the request is rejected. * The search range are blocks between heights including `minimalBlockNumber` and excluding `firstOverflowBlockNumber`. * If the verifier does not have a view of all blocks from `minimalBlockNumber` to `firstOverflowBlockNumber`, * the attestation request is rejected. * * The request is confirmed if no transaction meeting the specified criteria is found in the search range. * The criteria and timestamp are chain specific. * ### UTXO (Bitcoin and Dogecoin) * * * Criteria for the transaction: * * * - It is not coinbase transaction. * - The transaction has the specified standardPaymentReference. * - The sum of values of all outputs with the specified address minus the sum of values of all inputs with * the specified address is greater than `amount` (in practice the sum of all values of the inputs with the * specified address is zero). * * * Timestamp is `mediantime`. * ### XRPL * * * * Criteria for the transaction: * - The transaction is of type payment. * - The transaction has the specified standardPaymentReference, * - One of the following is true: * - Transaction status is `SUCCESS` and the amount received by the specified destination address is * greater than the specified `value`. * - Transaction status is `RECEIVER_FAILURE` and the specified destination address would receive an * amount greater than the specified `value` had the transaction been successful. * * * Timestamp is `close_time` converted to UNIX time. * * @custom:lut `minimalBlockTimestamp` * @custom:lutlimit `0x127500`, `0x127500`, `0x127500` */ interface IReferencedPaymentNonexistence { /** * @notice Toplevel request * @param attestationType ID of the attestation type. * @param sourceId ID of the data source. * @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response as defined. * @param requestBody Data defining the request. Type and interpretation is determined by the `attestationType`. */ struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } /** * @notice Toplevel response * @param attestationType Extracted from the request. * @param sourceId Extracted from the request. * @param votingRound The ID of the FDC round in which the request was considered. * @param lowestUsedTimestamp The lowest timestamp used to generate the response. * @param requestBody Extracted from the request. * @param responseBody Data defining the response. The verification rules for the construction of the response * body and the type are defined per specific `attestationType`. */ struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } /** * @notice Toplevel proof * @param merkleProof Merkle proof corresponding to the attestation response. * @param data Attestation response. */ struct Proof { bytes32[] merkleProof; Response data; } /** * @notice Request body for ReferencePaymentNonexistence attestation type * @param minimalBlockNumber The start block of the search range. * @param deadlineBlockNumber The blockNumber to be included in the search range. * @param deadlineTimestamp The timestamp to be included in the search range. * @param destinationAddressHash The standard address hash of the address to which the payment had to be done. * @param amount The requested amount in minimal units that had to be paid. * @param standardPaymentReference The requested standard payment reference. * @param checkSourceAddresses If true, the source address root is checked (only full match). * @param sourceAddressesRoot The root of the Merkle tree of the source addresses. * @custom:below The `standardPaymentReference` should not be zero (as a 32-byte sequence). */ struct RequestBody { uint64 minimalBlockNumber; uint64 deadlineBlockNumber; uint64 deadlineTimestamp; bytes32 destinationAddressHash; uint256 amount; bytes32 standardPaymentReference; bool checkSourceAddresses; bytes32 sourceAddressesRoot; } /** * @notice Response body for ReferencePaymentNonexistence attestation type. * @param minimalBlockTimestamp The timestamp of the minimalBlock. * @param firstOverflowBlockNumber The height of the firstOverflowBlock. * @param firstOverflowBlockTimestamp The timestamp of the firstOverflowBlock. * @custom:below `firstOverflowBlock` is the first block that has block number higher than * `deadlineBlockNumber` and timestamp later than `deadlineTimestamp`. * The specified search range are blocks between heights including `minimalBlockNumber` * and excluding `firstOverflowBlockNumber`. */ struct ResponseBody { uint64 minimalBlockTimestamp; uint64 firstOverflowBlockNumber; uint64 firstOverflowBlockTimestamp; } } ``` ## Structs ### Request Toplevel request structure. #### Parameters - `attestationType`: ID of the attestation type (0x04 for ReferencedPaymentNonexistence) - `sourceId`: ID of the data source (e.g., BTC, DOGE, XRP) - `messageIntegrityCode`: MessageIntegrityCode derived from the expected response - `requestBody`: Data defining the request ### Response Toplevel response structure. #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Toplevel proof structure for verification. #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody Request body specific to payment nonexistence verification. #### Parameters - `minimalBlockNumber`: The starting block number of the search range - `deadlineBlockNumber`: The last block number to include in the search range - `deadlineTimestamp`: The timestamp deadline for the payment - `destinationAddressHash`: Standard hash of the address that should have received the payment - `amount`: Minimum amount that should have been received - `standardPaymentReference`: The specific payment reference that should have been included - `checkSourceAddresses`: If true, source addresses are also verified - `sourceAddressesRoot`: Merkle tree root of expected source addresses (if checked) ### ResponseBody Response body containing search range details. #### Parameters - `minimalBlockTimestamp`: Timestamp of the first block in the search range - `firstOverflowBlockNumber`: Block number of the first block after the deadline - `firstOverflowBlockTimestamp`: Timestamp of the first block after the deadline ## Block Range Definition The search range for payment verification is defined as: - **Start**: Block at `minimalBlockNumber` (inclusive) - **End**: Block at `firstOverflowBlockNumber` (exclusive) The `firstOverflowBlock` is determined as the first block that: 1. Has a block number greater than `deadlineBlockNumber` 2. Has a timestamp later than `deadlineTimestamp` This dual condition approach ensures that both block height and timestamp requirements are met. ## Implementation Notes - Attestation ID: `0x04` - The `lowestUsedTimestamp` parameter uses the value of `minimalBlockTimestamp` - The `lutlimit` (Lowest Used Timestamp limit) is `0x127500` (1,209,600 seconds = 14 days) for all supported chains - The standard payment reference must not be zero - Request will be rejected if the verification node doesn't have visibility of all blocks in the search range - Request will be rejected if `firstOverflowBlockNumber` cannot be determined or lacks sufficient confirmations ## Standard Payment Reference A standardized payment reference follows a specific format to ensure consistency across different blockchains: - For UTXO chains (BTC, DOGE): Derived from OP_RETURN outputs or specific patterns in transaction data - For XRPL: Derived from the MemoData field in the transaction The standardPaymentReference is critical for this attestation type as it uniquely identifies the specific payment being verified for nonexistence. ## Usage Example {PaymentDeadlineEnforcer} Open example in Remix --- ## IWeb2Json The `Web2Json` attestation type fetches data from a URL, post-processes the returned JSON with a `jq` filter, and returns the result as ABI-encoded bytes. The interface only declares the request/response/proof structs — verification is exposed via [`IFdcVerification.verifyWeb2Json`](/fdc/reference/IFdcVerification#verifyweb2json). Sourced from `IWeb2Json.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IWeb2Json.sol). ## Structs ### Request Top-level request. ```solidity struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } ``` ### Response Top-level response. ```solidity struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } ``` ### Proof Top-level proof. ```solidity struct Proof { bytes32[] merkleProof; Response data; } ``` ### RequestBody ```solidity struct RequestBody { string url; string httpMethod; // "GET" | "POST" | "PUT" | "PATCH" | "DELETE" string headers; // "{}" if none string queryParams; // "{}" if none string body; // "{}" if none string postProcessJq; string abiSignature; } ``` ### ResponseBody ```solidity struct ResponseBody { bytes abiEncodedData; } ``` Decode `abiEncodedData` into the struct described by your `abiSignature` to consume the post-processed payload on-chain. --- ## IWeb2JsonVerification `IWeb2JsonVerification` is the verifier surface for the [`Web2Json`](/fdc/reference/IWeb2Json) attestation type. It is one of the parent interfaces inherited by [`IFdcVerification`](/fdc/reference/IFdcVerification) — in practice most consumers call `verifyWeb2Json` through `ContractRegistry.getFdcVerification()` rather than holding a separate `IWeb2JsonVerification` reference. Sourced from `IWeb2JsonVerification.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IWeb2JsonVerification.sol). ## Functions ### verifyWeb2Json ```solidity function verifyWeb2Json(IWeb2Json.Proof calldata _proof) external view returns (bool _proved); ``` **Parameters** - `_proof`: The [`IWeb2Json.Proof`](/fdc/reference/IWeb2Json#proof) returned by the Data Availability Layer. **Returns** - `_proved`: `true` if the merkle proof is consistent with the round's Merkle root maintained by the FDC `Relay`. --- ## IXRPPayment An interface to relay and verify XRP Ledger `Payment` transactions, exposing XRPL-specific fields (source `r`-address, first Memo data, destination tag) directly to consumer contracts. Sourced from `IXRPPayment.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IXRPPayment.sol). ## Overview The `IXRPPayment` interface lets smart contracts on Flare verify native XRP payments using XRPL-native request fields. It surfaces the XRPL `r`-address as a string, the raw bytes of the first Memo's `MemoData`, and the `DestinationTag`. This is the proof type consumed, for example, by FAssets direct minting (`executeDirectMinting(IXRPPayment.Proof calldata _payment)`). ## Supported Chains | Network Type | Supported Chains | | ------------ | ------------------------ | | **Mainnet** | `XRP` (XRP Ledger) | | **Testnet** | `testXRP` (XRPL Testnet) | ## Interface Definition ```solidity // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name IXRPPayment * @custom:id 0x08 * @custom:supported XRP, testXRP * @author Flare * @notice A relay of a transaction on an XRPL chain that is of type payment in a native (XRP) currency. * The provable transaction is identified by its `transactionId`. The transactions represents a transfer * / attempt of transfer of XRP currency from a source address to a receiving address, and it also includes * relevant details such as amount sent, amount received, memos, destination tags, and success status. * * @custom:verification The transaction with `transactionId` is fetched from the RPC of the blockchain node * or relevant indexer. * * If the transaction cannot be fetched or the transaction is in a block that does not have a sufficient * number of confirmations, the attestation request is rejected. * * Once the transaction is received, the payment summary is computed according to the rules for the source * chain. If the summary is successfully calculated, the response is assembled from the summary. * `blockNumber` and `blockTimestamp` are retrieved from the block if they are not included in the * transaction data. `blockTimestamp` is close time of the ledger converted to UNIX time. * * If the summary is not successfully calculated, the attestation request is rejected. * @custom:lut `blockTimestamp` * @custom:lutlimit `0x127500` */ interface IXRPPayment { struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } struct Proof { bytes32[] merkleProof; Response data; } struct RequestBody { bytes32 transactionId; address proofOwner; } struct ResponseBody { uint64 blockNumber; uint64 blockTimestamp; string sourceAddress; bytes32 sourceAddressHash; bytes32 receivingAddressHash; bytes32 intendedReceivingAddressHash; int256 spentAmount; int256 intendedSpentAmount; int256 receivedAmount; int256 intendedReceivedAmount; bool hasMemoData; bytes firstMemoData; bool hasDestinationTag; uint256 destinationTag; uint8 status; } } ``` ## Structs ### Request Top-level request structure. #### Parameters - `attestationType`: ID of the attestation type (`0x08` for `XRPPayment`) - `sourceId`: ID of the data source (`XRP` or `testXRP`) - `messageIntegrityCode`: `MessageIntegrityCode` derived from the expected response - `requestBody`: Data defining the request ### Response Top-level response structure. #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response (set to `blockTimestamp`) - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Top-level proof structure for verification. #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody #### Parameters - `transactionId`: Hash of the XRPL `Payment` transaction to verify - `proofOwner`: EVM address authorized to use the proof; lower-cased by the verifier ### ResponseBody #### Parameters - `blockNumber`: Ledger index containing the transaction - `blockTimestamp`: Ledger close time, converted to UNIX time - `sourceAddress`: The XRPL `r`-address of the sender - `sourceAddressHash`: Standard address hash of the source address - `receivingAddressHash`: Standard address hash of the receiver (zero `bytes32` if the transaction did not succeed) - `intendedReceivingAddressHash`: Standard address hash of the address in the `Destination` field, relevant for failed transactions - `spentAmount`: Drops actually spent by the source - `intendedSpentAmount`: Drops the source intended to spend (`Amount + Fee`) - `receivedAmount`: Drops received by the receiver - `intendedReceivedAmount`: Drops the receiver would have received on success - `hasMemoData`: `true` if the transaction has at least one Memo with `MemoData` - `firstMemoData`: Raw bytes of the first Memo's `MemoData`; empty when `hasMemoData` is `false` - `hasDestinationTag`: `true` if the transaction has a `DestinationTag` - `destinationTag`: The destination tag value (`0` when `hasDestinationTag` is `false`); XRPL only allows `uint32` values - `status`: Transaction status (`0` = SUCCESS, `1` = SENDER_FAILURE, `2` = RECEIVER_FAILURE) ## Transaction Status Codes | Status Code | Description | Explanation | | ----------- | ---------------- | ------------------------------------------------------- | | 0 | SUCCESS | Transaction completed successfully | | 1 | SENDER_FAILURE | Transaction failed due to issues on the sender's side | | 2 | RECEIVER_FAILURE | Transaction failed due to issues on the receiver's side | ## Implementation Notes - Attestation ID: `0x08` - The `lowestUsedTimestamp` parameter uses the value of `blockTimestamp` - The `lutlimit` (Lowest Used Timestamp limit) is `0x127500` (1,209,600 seconds = 14 days) - Only XRPL `Payment` transaction type produces a response; other transaction types are rejected - Multi-output payments (`tfPartialPayment` resulting in multiple deliveries) are rejected - `proofOwner` lets a contract bind a proof to a specific caller; pass `address(0)` when not needed --- ## IXRPPaymentNonexistence An interface to verify that no XRP Ledger `Payment` transaction matching the supplied destination, amount, memo, and/or destination tag was confirmed within a given ledger range. Sourced from `IXRPPaymentNonexistence.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/fdc/IXRPPaymentNonexistence.sol). ## Overview Where [`IReferencedPaymentNonexistence`](/fdc/reference/IReferencedPaymentNonexistence.mdx) matches transactions by a [standard payment reference](/fdc/attestation-types/referenced-payment-nonexistence.mdx#standard-payment-reference), `IXRPPaymentNonexistence` matches them by the **first Memo's `MemoData` hash** and/or the **`DestinationTag`** — the two correlating fields native XRPL applications already use. This is useful for invoicing flows where each payer is assigned a destination tag, and for memo-based protocols. ## Supported Chains | Network Type | Supported Chains | | ------------ | ------------------------ | | **Mainnet** | `XRP` (XRP Ledger) | | **Testnet** | `testXRP` (XRPL Testnet) | ## Verification Criteria A transaction is considered a **match** (and would invalidate the nonexistence claim) if **all** of the following hold: - The transaction is of type `Payment` and produces a successful payment summary with one sender and one receiver. - The receiver's standard address hash equals `destinationAddressHash`. - The intended receiving amount is greater than or equal to the requested `amount`. - The transaction did not fail with `SENDER_FAILURE` (so both `SUCCESS` and `RECEIVER_FAILURE` qualify, mirroring the XRPL behavior of `IReferencedPaymentNonexistence`). - If `checkFirstMemoData` is `true`: the standard hash of the first Memo's `MemoData` equals `firstMemoDataHash`. - If `checkDestinationTag` is `true`: the transaction has a `DestinationTag` and it equals `destinationTag`. The request is **confirmed** if no transaction in the search range matches all applicable criteria. :::warning[At least one match field is required] At least one of `checkFirstMemoData` or `checkDestinationTag` must be `true`. ::: ## Interface Definition ```solidity // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; /** * @custom:name IXRPPaymentNonexistence * @custom:id 0x09 * @custom:supported XRP, testXRP * @author Flare * @notice Assertion that an agreed-upon XRP payment has not been made by a certain deadline. * A confirmed request shows that a transaction meeting certain criteria (address, amount, reference) did not appear * in the specified block range. * * This type of attestation can be used to e.g. provide grounds to liquidate funds locked by a smart contract on * Flare when a payment is missed. * * @custom:verification If `firstOverflowBlock` cannot be determined or does not have a sufficient number of * confirmations, the attestation request is rejected. * * If `firstOverflowBlockNumber` is higher or equal to `minimalBlockNumber`, the request is rejected. * The search range are blocks between heights including `minimalBlockNumber` and excluding `firstOverflowBlockNumber`. * If the verifier does not have a view of all blocks from `minimalBlockNumber` to `firstOverflowBlockNumber`, * the attestation request is rejected. * * The request is confirmed if no transaction meeting the specified criteria is found in the search range. * * Criteria for the transaction: * - The transaction is of type payment. * - The destination address hash matches the hash of the destination address of the transaction. * - If `checkFirstMemoData` is true, the hash of the MemoData field of the first Memo in the transaction * matches `firstMemoDataHash`. * - If `checkDestinationTag` is true, the destination tag of the transaction matches `destinationTag`. * At least one of the fields `checkFirstMemoData` and `checkDestinationTag` must be true for the request * to be valid. * - One of the following is true: * - Transaction status is `SUCCESS` and the amount received by the specified destination address is greater * than the specified `value`. * - Transaction status is `RECEIVER_FAILURE` and the specified destination address would receive an amount * greater than the specified `value` had the transaction been successful. * * Timestamp is `close_time` converted to UNIX time. * * @custom:lut `minimalBlockTimestamp` * @custom:lutlimit `0x127500` */ interface IXRPPaymentNonexistence { struct Request { bytes32 attestationType; bytes32 sourceId; bytes32 messageIntegrityCode; RequestBody requestBody; } struct Response { bytes32 attestationType; bytes32 sourceId; uint64 votingRound; uint64 lowestUsedTimestamp; RequestBody requestBody; ResponseBody responseBody; } struct Proof { bytes32[] merkleProof; Response data; } struct RequestBody { uint64 minimalBlockNumber; uint64 deadlineBlockNumber; uint64 deadlineTimestamp; bytes32 destinationAddressHash; uint256 amount; bool checkFirstMemoData; bytes32 firstMemoDataHash; bool checkDestinationTag; uint256 destinationTag; address proofOwner; } struct ResponseBody { uint64 minimalBlockTimestamp; uint64 firstOverflowBlockNumber; uint64 firstOverflowBlockTimestamp; } } ``` ## Structs ### Request Top-level request structure. #### Parameters - `attestationType`: ID of the attestation type (`0x09` for `XRPPaymentNonexistence`) - `sourceId`: ID of the data source (`XRP` or `testXRP`) - `messageIntegrityCode`: `MessageIntegrityCode` derived from the expected response - `requestBody`: Data defining the request ### Response Top-level response structure. #### Parameters - `attestationType`: Extracted from the request - `sourceId`: Extracted from the request - `votingRound`: The ID of the FDC round in which the request was considered - `lowestUsedTimestamp`: The lowest timestamp used to generate the response (set to `minimalBlockTimestamp`) - `requestBody`: Extracted from the request - `responseBody`: Data defining the response ### Proof Top-level proof structure for verification. #### Parameters - `merkleProof`: Merkle proof corresponding to the attestation response - `data`: Attestation response ### RequestBody #### Parameters - `minimalBlockNumber`: First ledger of the search range (inclusive) - `deadlineBlockNumber`: Ledger to be included as the end of the search range - `deadlineTimestamp`: Timestamp to be included as the end of the search range - `destinationAddressHash`: Standard address hash of the address that should have received the payment - `amount`: Minimum amount in drops that should have been received - `checkFirstMemoData`: When `true`, only transactions whose first Memo's `MemoData` hash equals `firstMemoDataHash` qualify as a match - `firstMemoDataHash`: Hash of the expected first Memo's `MemoData` - `checkDestinationTag`: When `true`, only transactions whose `DestinationTag` equals `destinationTag` qualify as a match - `destinationTag`: Expected destination tag (XRPL only supports `uint32` values) - `proofOwner`: EVM address authorized to use the proof; lower-cased by the verifier ### ResponseBody #### Parameters - `minimalBlockTimestamp`: Timestamp of the ledger at `minimalBlockNumber` - `firstOverflowBlockNumber`: First ledger past `deadlineBlockNumber` and `deadlineTimestamp` - `firstOverflowBlockTimestamp`: Timestamp of `firstOverflowBlockNumber` ## Block Range Definition The search range is `[minimalBlockNumber, firstOverflowBlockNumber)`. `firstOverflowBlockNumber` is the first ledger that has both a height greater than `deadlineBlockNumber` **and** a timestamp later than `deadlineTimestamp`. ## Implementation Notes - Attestation ID: `0x09` - The `lowestUsedTimestamp` parameter uses the value of `minimalBlockTimestamp` - The `lutlimit` (Lowest Used Timestamp limit) is `0x127500` (1,209,600 seconds = 14 days) - At least one of `checkFirstMemoData` or `checkDestinationTag` must be `true` - Request is rejected if the verifier lacks visibility of all ledgers in the search range - Request is rejected if `firstOverflowBlockNumber` cannot be determined or lacks sufficient confirmations --- ## Data Availability Api(Reference) --- ## IFdcHub Primary interface for interacting with the Flare Data Connector (FDC). Sourced from `IFdcHub.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFdcHub.sol). ## Overview The IFdcHub interface serves as the main entry point for applications requesting attestations from the Flare Data Connector. It provides functionality to request attestations, access configuration contracts, and handle related events. ## Functions ### fdcInflationConfigurations Returns the FDC inflation configurations contract address. ```solidity function fdcInflationConfigurations( ) external view returns ( contract IFdcInflationConfigurations ); ``` **Returns** - `IFdcInflationConfigurations`: Contract interface for accessing inflation configurations ### fdcRequestFeeConfigurations Returns the FDC request fee configurations contract address. ```solidity function fdcRequestFeeConfigurations( ) external view returns ( contract IFdcRequestFeeConfigurations ); ``` **Returns** - `IFdcRequestFeeConfigurations`: Contract interface for accessing request fee configurations ### requestAttestation Requests an attestation from the Flare Data Connector. ```solidity function requestAttestation( bytes _data ) external payable; ``` **Parameters** - `_data`: ABI encoded attestation request **Note**: This function is payable and requires a fee based on the attestation type. ### requestsOffsetSeconds Returns the offset (in seconds) for the requests to be processed during the current voting round. ```solidity function requestsOffsetSeconds( ) external view returns ( uint8 ); ``` **Returns** - `uint8`: Offset in seconds ## Events ### AttestationRequest Emitted when an attestation request is submitted. ```solidity event AttestationRequest( bytes data, uint256 fee ) ``` **Parameters** - `data`: The encoded attestation request data - `fee`: The amount paid for the attestation request ### InflationRewardsOffered Emitted when inflation rewards are offered. ```solidity event InflationRewardsOffered( uint24 rewardEpochId, struct IFdcInflationConfigurations.FdcConfiguration[] fdcConfigurations, uint256 amount ) ``` **Parameters** - `rewardEpochId`: The ID of the reward epoch - `fdcConfigurations`: Array of FDC configurations - `amount`: The total amount of rewards offered ### RequestsOffsetSet Emitted when the requests offset is updated. ```solidity event RequestsOffsetSet( uint8 requestsOffsetSeconds ) ``` **Parameters** - `requestsOffsetSeconds`: The new offset value in seconds ## Usage Example {AddressSolidity} Open example in Remix --- ## IFdcInflationConfigurations Interface for managing Flare Data Connector (FDC) inflation configuration. Sourced from `IFdcInflationConfigurations.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFdcInflationConfigurations.sol). ## Overview The IFdcInflationConfigurations interface allows access to the inflation distribution settings for different attestation types and sources within the Flare Data Connector system. These configurations determine how inflation rewards are allocated to validators based on the attestation requests they process. ## Functions ### getFdcConfiguration Returns a single FDC configuration at the specified index. ```solidity function getFdcConfiguration( uint256 _index ) external view returns ( struct IFdcInflationConfigurations.FdcConfiguration ); ``` **Parameters** - `_index`: The index of the FDC configuration **Returns** - `FdcConfiguration`: The configuration struct at the specified index ### getFdcConfigurations Returns the complete array of all FDC configurations. ```solidity function getFdcConfigurations( ) external view returns ( struct IFdcInflationConfigurations.FdcConfiguration[] ); ``` **Returns** - `FdcConfiguration[]`: Array of all configured FDC configurations ## Structures ### FdcConfiguration Configuration structure for FDC inflation settings per attestation type and source. ```solidity struct FdcConfiguration { bytes32 attestationType; bytes32 source; uint24 inflationShare; uint8 minRequestsThreshold; uint224 mode; } ``` **Fields** - `attestationType`: Identifier for the attestation type (e.g., AddressValidity, BalanceDecreasingTransaction) - `source`: The data source identifier (e.g., BTC, DOGE, XRP) - `inflationShare`: Percentage share of the total inflation allocated to this configuration (basis points) - `minRequestsThreshold`: Minimum number of requests required for this configuration to receive inflation rewards - `mode`: Additional configuration flags and settings ## Usage Example {InflationMonitor} Open example in Remix --- ## IFdcRequestFeeConfigurations Interface for managing FDC request fee configuration. Sourced from `IFdcRequestFeeConfigurations.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFdcRequestFeeConfigurations.sol). ## Overview The IFdcRequestFeeConfigurations interface provides functionality for managing and retrieving fees associated with attestation requests in the Flare Data Connector (FDC) system. It allows for querying the base fee required for specific attestation requests. ## Functions ### getRequestFee Method to get the base fee for an attestation request. It reverts if the request is not supported. ```solidity function getRequestFee( bytes _data ) external view returns ( uint256 ); ``` **Parameters** - `_data`: ABI encoded attestation request **Returns** - `uint256`: The base fee required for the specified attestation request ## Events ### TypeAndSourceFeeRemoved Emitted when a fee configuration for a specific attestation type and source is removed. ```solidity event TypeAndSourceFeeRemoved( bytes32 attestationType, bytes32 source ) ``` **Parameters** - `attestationType`: The type of attestation - `source`: The source identifier ### TypeAndSourceFeeSet Emitted when a fee configuration for a specific attestation type and source is set. ```solidity event TypeAndSourceFeeSet( bytes32 attestationType, bytes32 source, uint256 fee ) ``` **Parameters** - `attestationType`: The type of attestation - `source`: The source identifier - `fee`: The fee amount set for the attestation type and source ## Usage Example {FeeChecker} Open example in Remix --- ## IFdcVerification Interface for verifying Flare Data Connector (FDC) attestation requests. Sourced from `IFdcVerification.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/contracts/userInterfaces/IFdcVerification.sol). ## Overview The IFdcVerification interface provides methods to verify different types of attestations from the Flare Data Connector. Smart contracts can use these verification functions to validate proofs provided by the FDC, ensuring the authenticity and integrity of the external data being used. ## Verification Functions Each verification function takes a proof structure specific to the attestation type and returns a boolean indicating whether the proof is valid. ### verifyAddressValidity Verifies a proof for an address validity attestation. ```solidity function verifyAddressValidity( struct IAddressValidity.Proof _proof ) external view returns ( bool _proved ); ``` **Parameters** - `_proof`: The address validity proof structure containing the merkle proof and response data **Returns** - `_proved`: Boolean indicating if the proof is valid ### verifyBalanceDecreasingTransaction Verifies a proof for a balance decreasing transaction attestation. ```solidity function verifyBalanceDecreasingTransaction( struct IBalanceDecreasingTransaction.Proof _proof ) external view returns ( bool _proved ); ``` **Parameters** - `_proof`: The balance decreasing transaction proof structure **Returns** - `_proved`: Boolean indicating if the proof is valid ### verifyConfirmedBlockHeightExists Verifies a proof that a specified block height exists and is confirmed. ```solidity function verifyConfirmedBlockHeightExists( struct IConfirmedBlockHeightExists.Proof _proof ) external view returns ( bool _proved ); ``` **Parameters** - `_proof`: The confirmed block height existence proof structure **Returns** - `_proved`: Boolean indicating if the proof is valid ### verifyEVMTransaction Verifies a proof for an Ethereum Virtual Machine transaction. ```solidity function verifyEVMTransaction( struct IEVMTransaction.Proof _proof ) external view returns ( bool _proved ); ``` **Parameters** - `_proof`: The EVM transaction proof structure **Returns** - `_proved`: Boolean indicating if the proof is valid ### verifyPayment Verifies a proof for a payment transaction. ```solidity function verifyPayment( struct IPayment.Proof _proof ) external view returns ( bool _proved ); ``` **Parameters** - `_proof`: The payment proof structure **Returns** - `_proved`: Boolean indicating if the proof is valid ### verifyReferencedPaymentNonexistence Verifies a proof that a specific payment with reference did not occur within a given timeframe. ```solidity function verifyReferencedPaymentNonexistence( struct IReferencedPaymentNonexistence.Proof _proof ) external view returns ( bool _proved ); ``` **Parameters** - `_proof`: The referenced payment nonexistence proof structure **Returns** - `_proved`: Boolean indicating if the proof is valid ### verifyWeb2Json Verifies a proof for a Web2Json attestation (FDC-fetched HTTP response post-processed through JQ). ```solidity function verifyWeb2Json( struct IWeb2Json.Proof _proof ) external view returns ( bool _proved ); ``` **Parameters** - `_proof`: The Web2Json proof structure **Returns** - `_proved`: Boolean indicating if the proof is valid ## Metadata accessors ### fdcProtocolId Returns the FDC protocol id used as the `protocolId` argument when querying `IRelay.isFinalized(protocolId, votingRoundId)`. Read this at runtime instead of hard-coding a literal — the value can change between network releases. ```solidity function fdcProtocolId() external view returns (uint8 _fdcProtocolId); ``` ### relay Returns the `IRelay` contract address bound to this `IFdcVerification` deployment. ```solidity function relay() external view returns (IRelay); ``` ## Usage Example {AddressSolidity} Open example in Remix --- ## FAssets FAssets is a trustless, over-collateralized bridge connecting non smart contract networks to Flare. It enables the creation of wrapped tokens (`FAssets`) for assets like BTC, DOGE and XRP. These tokens can participate in Flare's DeFi ecosystem or be redeemed for their original assets. FAssets are powered by Flare's enshrined data protocols: - **[Flare Time Series Oracle (FTSO)](/ftso/overview):** Provides decentralized price feeds. - **[Flare Data Connector (FDC)](/fdc/overview):** Verifies offchain actions, such as transactions on other blockchains. Each FAsset is backed by a mix of collateral, including: 1. Stablecoin or ETH collateral. 2. FLR (Flare's native token) or SGB (Songbird's native token) collateral. Agents and a community-provided collateral pool ensure trustlessness through over-collateralization. ## Minting Resources ### Minting dApps You can mint FAssets on either of the following dApps: - [https://fasset.oracle-daemon.com/flare](https://fasset.oracle-daemon.com/flare) - [https://fassets.au.cc](https://fassets.au.cc) ### Supported Wallets | Wallet | | | Notes | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [Bifrost](https://bifrostwallet.com/) | ✅ | ✅ | Supports both networks. [Tutorial](https://www.youtube.com/watch?v=1lETSE9YlXQ) | | [Ledger](https://www.ledger.com/) | ✅ | ✅ | Hardware wallet supporting both networks. [Tutorial](https://youtu.be/WkdWZJJBem0) | | [Luminite](https://luminite.app/) | ✅ | ✅ | Integrated FAssets support with DeFi utilities | | [OxenFlow](https://oxenflow.io/) | ✅ | ✅ | Available via iOS app | | [MetaMask](https://metamask.io/) | ✅ | | EVM wallet for Flare transactions | | [Rabby](https://rabby.io/) | ✅ | | EVM wallet for Flare transactions | | EVM Wallets with [WalletConnect](https://walletconnect.com/) | ✅ | | Any EVM-compatible wallet supporting WalletConnect | | [Xaman](https://xaman.app/) | | ✅ | XRPL wallet only. [Tutorial](https://youtu.be/-3TPLIszPUc) | :::info To complete the full minting process, you may need: - A **Flare-compatible wallet** (e.g., MetaMask, Bifrost, Luminite) to interact with FAssets smart contracts. - An **XRPL-compatible wallet** (e.g., Xaman, Bifrost, Luminite) to send the underlying XRP to the agent. Wallets that support both networks (Bifrost, Ledger, Luminite, OxenFlow) provide the most seamless experience. ::: ## FAsset Workflow Anyone on the Flare blockchain can mint FAssets, which are wrapped versions of original tokens from other blockchains, known as underlying networks. The original tokens from these chains, such as Ripple (XRPL), Dogecoin (DOGE), Bitcoin (BTC), and Litecoin (LTC), are referred to as underlying assets. For example, the FAsset version of Bitcoin is known as FBTC. ### Minting - A user (minter) selects an agent and pays a fee to reserve collateral. - The user sends the underlying asset (e.g., BTC) to the agent. - The FDC verifies the transaction. - The equivalent FAssets (e.g., FBTC) are minted as ERC-20 tokens on Flare. ### Usage Minted FAssets can be used in DeFi applications on Flare or bridged to other chains. ### Redeeming Users can redeem FAssets for the original underlying assets at any time. ## Key Participants ### Agents Agents manage the infrastructure and operations of the FAssets system, including: - Holding the underlying assets. - Providing collateral for minting and redemption. - Redeeming underlying assets for users. Each agent is verified through governance and uses the following addresses on the native chain: - **Work Address:** A hot wallet for executing operations. - **Management Address:** A cold wallet for secure administrative actions. Agents must comply with the **backing factor**, which ensures sufficient collateral is locked to back FAssets. ### Users Users interact with the system by: - **Minting:** Depositing underlying assets to mint FAssets. - **Redeeming:** Exchanging FAssets for the original underlying assets. Eligibility: - No restrictions—anyone can mint or redeem FAssets. ### Collateral Providers Collateral providers supply native FLR tokens to an agent's collateral pool and earn a share of minting fees as long as their tokens remain locked. ### Liquidators Liquidators maintain system health by: - Burning FAssets in exchange for collateral when an agent's collateral drops below the required minimum. - Earning rewards, including premiums on the collateral received. Eligibility: - Open to all—anyone can become a liquidator. ### Challengers Challengers monitor agents for illegal transactions that reduce collateral below the backing factor. They: - Submit proof of illegal actions to the system. - Earn rewards from the agent's vault upon successful challenges. If an agent is found in violation, they enter **full liquidation**, permanently restricting them from new minting operations. ## Core Vault The **Core Vault (CV)** is a specialized FAsset system component that enhances capital efficiency by allowing agents to store underlying assets without requiring additional collateral. Each asset type has its own dedicated Core Vault, which is managed by a multisig account on the underlying network under formal governance oversight. ### Key Features - **Collateral Efficiency:** Agents transferring assets to the CV free up collateral, allowing them to mint additional FAssets or withdraw funds. - **Redemption Support:** The CV ensures that underlying assets are available for redemptions, reducing reliance on individual agents. - **Security & Governance:** A multisig setup controls the vault, and governance can pause in case of security concerns. ### Core Vault Implementation On networks without smart contracts (e.g., XRP Ledger), the Core Vault is a **multisig account** managed by signers authorized by Flare governance. Movements of funds require multiple signatures and follow formal agreements, not individual agent control. ### Agent vs. Core Vault Ownership - **Agents:** Hold and control their own underlying assets in wallets as part of collateral. - **Core Vault:** Holds pooled assets that no single agent owns; agents can request assets but cannot directly control the vault. This improves capital efficiency and liquidity for the system. :::tip[What's next] Learn more about the different components and processes involved in FAssets - [collateral](/fassets/collateral), [minting](/fassets/minting), [redemptions](/fassets/redemption), [liquidations](/fassets/liquidation) and [Core Vault](/fassets/core-vault). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## Minting Minting FAssets is the process of wrapping underlying tokens from connected blockchains into FAssets to be used on the Flare blockchain. Any user can mint FAssets. ## Minting Resources ### Minting dApps You can mint FAssets on either of the following dApps: - [https://fasset.oracle-daemon.com/flare](https://fasset.oracle-daemon.com/flare) - [https://fassets.au.cc](https://fassets.au.cc) ### Supported Wallets | Wallet | | | Notes | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | [Bifrost](https://bifrostwallet.com/) | ✅ | ✅ | Supports both networks. [Tutorial](https://www.youtube.com/watch?v=1lETSE9YlXQ) | | [Ledger](https://www.ledger.com/) | ✅ | ✅ | Hardware wallet supporting both networks. [Tutorial](https://youtu.be/WkdWZJJBem0) | | [Luminite](https://luminite.app/) | ✅ | ✅ | Integrated FAssets support with DeFi utilities | | [OxenFlow](https://oxenflow.io/) | ✅ | ✅ | Available via iOS app | | [MetaMask](https://metamask.io/) | ✅ | | EVM wallet for Flare transactions | | [Rabby](https://rabby.io/) | ✅ | | EVM wallet for Flare transactions | | EVM Wallets with [WalletConnect](https://walletconnect.com/) | ✅ | | Any EVM-compatible wallet supporting WalletConnect | | [Xaman](https://xaman.app/) | | ✅ | XRPL wallet only. [Tutorial](https://youtu.be/-3TPLIszPUc) | :::info To complete the full minting process, you may need: - A **Flare-compatible wallet** (e.g., MetaMask, Bifrost, Luminite) to interact with FAssets smart contracts. - An **XRPL-compatible wallet** (e.g., Xaman, Bifrost, Luminite) to send the underlying XRP to the agent. Wallets that support both networks (Bifrost, Ledger, Luminite, OxenFlow) provide the most seamless experience. ::: ## Minting Process This is the summary of the minting process: ### 1. Reserving Collateral The minter chooses an agent from the publicly available [agent list](/fassets/overview#agents). The choice is based on the minting fee or the amount of free collateral, which must be enough to back the amount to be minted. The minter sends to the Asset Manager contract a collateral reservation transaction (CRT). The CRT includes: - The address of the chosen agent. - The amount to mint, which must be a positive integer of [lots](/fassets/minting#lots). - The [collateral reservation fee (CRF)](#fees) to compensate for the locked collateral. - The executor's address, if the minter is not the executor. - The executor's fee, if the minter is not the executor. The Asset Manager contract locks the agent's collateral in the amount needed to back the whole minting until the underlying payment is proved or disproved. The collateral reservation response is an event issued by the contract, which includes: - The agent's address to which the minter must send funds on the underlying chain. - The amount to be paid on the underlying chain, which corresponds to the amount to be minted plus the agent's fee. - The payment reference, which is a unique 32-byte number the minter must include as a memo in the payment on the underlying chain. - The last underlying block and the last underlying timestamp to pay. Valid payments occur either before the last block or before the last timestamp, both inclusive. - The executor's address, if the minter is not the executor. - The executor's fee, if the minter is not the executor. The time to pay is measured both in the underlying chain's block numbers and block times because the underlying chain might halt for a long time. In this situation, the block numbers do not increment but the block timestamps do. ### 2. Underlying Payment After this event is emitted, the minter must pay the full underlying amount plus the fee to the agent on the underlying chain. This payment must be completed within a specified time limit. ### 3. Payment Proof Using the [Flare Data Connector](/fdc/overview), the minter or executor proves the payment on Flare network. ### 4. Minting Execution After the payment is proved, the minter or executor executes the minting process, which sends FAssets to the minter's account. When minting is executed, the [minting fee](#fees) is split between the agent and the pool: - The percentage split is set by the agent. - The agent's share increases the free balance on the agent's underlying address. The free balance is the part of the balance in an agent's underlying address that the agent can withdraw. It is composed of minting fees, redemption fees, and self-closed FAssets. - The pool share gets minted as FAssets and credited to the collateral pool contract. After minting is complete, the Asset Manager creates a [redemption ticket](#redemption-tickets-and-the-redemption-queue), which includes the mint amount and the name of the agent backing the minting. ## Executor Role The execution of the minting process can be performed by an **executor**, an external actor such as a bot or service that monitors pending minting requests. Executors are incentivized to act quickly and correctly, but they hold no special permissions. If they fail to execute in time, the request may expire, and the minting must be restarted. The executor: - Is nominated by the minter and gets paid by the minter. - Uses the Flare Data Connector to obtain valid payment proof. - Executes the minting with a valid payment proof. ## Fees These fees are incurred by the **minter** when creating new FAssets: | Fee Name | Charged In | Status | Purpose | | :----------------------------------- | :--------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------- | | **Collateral Reservation Fee (CRF)** | Native Currency (FLR) | Obligatory | Compensates the Agent and collateral providers for locked collateral time. | | **Minting Fee** | Underlying Currency (e.g., XRP for FXRP) | Obligatory | The main source of revenue for the Agent and collateral providers. | | **Executor Fee** | FLR (from CRF remainder) | Optional | Compensates an optional executor who triggers minting execution, saving the minter extra operations. | ### Collateral Reservation Fee The **collateral reservation fee (CRF)** is paid in native tokens by the minter at the same time the [collateral reservation](#minting-process) is made. The CRF is defined by governance as a percentage of the minted value, and the same fee applies to all agents. The purpose of the CRF is to compensate the agent and collateral pool token (CPT) holders for the time their collateral is locked during the minting process. - If the minter does not pay on the underlying chain, the CRF is distributed to the agent and the pool in the same share as the minting fee. - If the minter successfully pays on the underlying chain, the CRF is also distributed to the agent and the pool in the same manner. For underlying chains where proving payments takes longer, the CRF might be set higher to account for the extended lock-up time. The CRF percentage is defined by governance and may vary based on the performance of the underlying chain. ### Minting Fee The **minting fee** is paid by the minter with the underlying currency as a percentage of the minted amount, and each agent can declare a different fee value. This fee is the main source of revenue for the agent and the CPT holders. The minting fee is further divided in two shares: #### Agent share This share remains in the agent's underlying account but is not marked as being in use. The agent can use this balance freely. #### Pool share This share is minted as FAssets and sent to the [collateral pool](/fassets/collateral#pool-collateral). The percentage of this share is defined by the agent and can be changed by the agent after a delay that provides time for minters to notice the change. ### Executor Fee To incentivize reliable execution of minting requests, an **executor fee** may be included in the system. The executor is the actor who submits the payment proof to the Asset Manager, finalizing the minting process. - The executor fee is paid by the minter when minting is executed. - This fee is optional and configurable within the system based on chain-specific governance parameters. - If set, the fee is denominated in FLR and transferred directly to the executor's address as part of the execution transaction. - Executors compete to be the first to execute minting and collect this fee, providing a decentralized execution layer. This design ensures timely and reliable minting finalization without relying on a centralized party. ### Minting The FAssets agent verifies the minter after the user completes the collateral reservation and pays the collateral reservation fee. The agent is responsible for confirming or rejecting the minter's status. If the agent does not respond within a certain timeframe, the minter has the option to cancel the reservation and receive a full refund of the collateral reservation fee. To enable the agent to verify the minter, the collateral reservation must include the address (or multiple addresses, in the case of UTXO chains) from which the payment will be made. If multiple addresses are provided, all of them must be used for the payment. Users must wait up to 60 seconds before they can cancel their request. If the agent accepts within this time, the user can proceed to mint by depositing the underlying assets. Therefore, it is important for the agent to respond quickly. If the agent does not respond in time, it will depend on whether the user is willing to wait; otherwise, the agent will simply miss the opportunity to mint, but there will be no loss of tokens. When the agent rejects the minter's request or the minter decides to cancel, the minter will receive a refund of the collateral reservation fee, minus a small percentage (e.g., 5%) that is burned. This burned amount is designed to prevent abuse of the agent by stopping someone from repeatedly reserving collateral from a sanctioned address. If the burned percentage were zero, an attacker could exploit the system without any cost. ## Payment Failure To finalize the minting, the minter must pay the agent on the underlying chain and prove the payment was received. If the payment is not completed in the time frame defined by the underlying chain block and timestamp, the agent must prove nonpayment to release the locked collateral. After nonpayment is proved, the agent's collateral that was reserved by the [CRT](#minting-process) is released, and the agent receives the [CRF](#collateral-reservation-fee). The [agent's registration process](/fassets/overview#agents) verifies that the agent's underlying address does not purposefully block payments and illegally collects the CRF. The following example shows proof of nonpayment.
Referenced payment nonexistence attestation type example. The following example shows how the nonpayment proof works. The Flare Data Connector's [referenced payment nonexistence attestation type](/fdc/attestation-types/referenced-payment-nonexistence) proves nonpayment. 1. The minter sends a request to mint FBTC. At the time the request is received, the last mined block on the Bitcoin chain is number 92, with timestamp 09:00 AM. The Asset Manager answers with the following threshold settings to complete the payment: - Block 100 - Timestamp 11:00 AM 2. Block 101 is mined with timestamp 10:59 AM. At this point, the payment can still happen. 3. Block 102 is mined with timestamp 11:04 AM. Payment did not occur. After this block is finalized, nonpayment can be proved. 4. Block 109 is mined. In this case, 7 blocks on the Bitcoin blockchain are enough blocks to assume finality. 5. The agent sends a nonpayment attestation request, which includes the payment reference, the underlying amount that was expected, the last block (100), and the last timestamp (11:00). 6. Attestation providers attest to the following facts: - Block 102 is finalized and has both the number and timestamp larger than required. - Before this block, the required payment either was not made or was not sufficient. Now, the mint-payment failure and the nonpayment proof can be submitted to the FAssets system.
## Edge Cases ### Unresponsive minter After a successful payment, the minter might not provide the payment proof needed to complete the minting process. In this case, the agent can present the payment proof and execute minting at any time. FAssets are still transferred to the minter's account, and the agent's collateral becomes redeemable. ### Expired proof Proofs provided by the [Flare Data Connector](/fdc/overview) can be created for 14 days and remain valid indefinitely once created. Suppose neither the minter nor the agent presents the proof of payment or nonpayment within the 14-day window. In that case, the regular minting process cannot continue, and the agent's collateral could be locked indefinitely. In this case, the agent can still recover the collateral by buying it back with native tokens. The recovery is accomplished with the following procedure: 1. Request the proof from the time when the deposit should have happened. The Flare Data Connector's answer will indicate that payments proofs are no longer available for that time. 2. Provide the amount of FLR collateral equivalent to the price of the underlying assets that should have been deposited. 3. Present the proof. Because a successful deposit cannot be proven, the FAssets system burns the amount of collateral in native tokens provided by the agent. After the burn is complete, the rest of the agent's collateral is released, both from his vault and the collateral pool. :::warning Note that this procedure should be used only in rare cases because providing timely payment or nonpayment proofs is always more advantageous for agents. ::: ## Duration of the Minting Process The duration of the minting process depends mainly on the speed of the underlying chain. The maximum duration of the process is the sum of: - A system-defined maximum time for deposit. It is either a few blocks on the underlying chain or a few minutes, whichever is longer. - The underlying chain's finalization time. - The Flare Data Connector proof time, which is approximately 3 - 5 minutes, independent of the underlying chain. On fast chains like XRPL, the maximum total time is less than 10 minutes, while on Bitcoin it is approximately 1.5 hours. For payment failures, the agent needs to wait the maximum time, as defined above, before the nonpayment proof can be retrieved. ## Minting Payment Reference The system generates a unique payment reference at the time of the collateral reservation request. The minter must include the payment reference in a memo field when the underlying payment transaction is made. The payment reference ensures the payment transaction cannot be used by another entity that might claim to have made the payment on the underlying chain and receive the minted FAssets in return. Additionally, if the payment time expires before payment is done, the agent can prove that no payment with that reference was made. A similar payment reference for the same purposes is generated for [redemptions](/fassets/redemption). ## Redemption Tickets and the Redemption Queue For every minting operation, a redemption ticket is created. This ticket references the minted amount and the agent that is backing the minting. The redemption tickets are ordered in a queue that determines the next agent to be [redeemed](/fassets/redemption) against according to the first in, first out method (FIFO). In other words, the first redemption ticket created will be the first redemption ticket processed. The FIFO queue impartially ensures that all agents have the opportunity to fulfill the duties of their role. The following example shows how the redemption queue works.
Redemption queue example. 1. Alice mints 10 FXRP with Agent 1. 2. Bob mints 20 FXRP with Agent 2. 3. Charlie mints 5 FXRP with Agent 1. After Alice, Bob, and Charlie have minted their FAssets, the redemption queue according to the FIFO method is: 1. Agent 1 with 10 FXRP. 2. Agent 2 with 20 FXRP. 3. Agent 1 with 5 FXRP. 4. Dana redeems 25 FXRP. To redeem 25 FXRP: 1. Agent 1 pays 10 FXRP. 2. Agent 2 pays 15 FXRP. Now, the redemption queue according to the FIFO method is: 1. Agent 2 with 5 FXRP. 2. Agent 1 with 5 FXRP.
## Lots Every minting and redemption must be made in a positive integer of lots. Lots serve the following purposes: - They prevent underlying transaction fees from exceeding minting or redemption fees. - They restrict large numbers of very small redemption tickets from being submitted, which would increase gas costs. Therefore, the amount of tokens in a lot (the _lot size_) varies for each underlying chain. For example, on the XRPL chain, a lot can be as small as 10 XRP because transaction fees are low. On the other hand, on the Bitcoin chain, lots might need to be as big as 0.25 BTC or more because transactions are far more expensive. Over time, the lot size can be updated to reflect price fluctuations of the underlying asset. Only a governance call can update the lot size, and it can be updated only by a limited amount per day. ## Dust Some processes generate a fractional number of lots: - On minting, part of the minting fee is minted as the FAsset fee to the collateral pool. This value is usually less than 1 lot. - When the lot size is changed, redemptions close only a positive integer of lots of each redemption ticket, which leaves the remainder unredeemed. These amounts, known as dust, cannot be redeemed directly because redemption requires a positive integer of lots. In such cases, the generated dust is not included in any redemption ticket. Instead, each agent's dust is accumulated until the dust amounts to a whole lot. When that happens, another redemption ticket is automatically created. Therefore, the dust can be recovered or destroyed in the following ways: - If the dust exceeds 1 lot during minting, the part that is a whole multiple of a lot is automatically added to the created redemption ticket. - If an agent does not mint any FAssets for a while but the lot size changes and several redemptions occur, enough dust might accumulate to more than 1 lot. In this case, the part that is a whole multiple of a lot can be converted to a redemption ticket by request. To prevent an inactive agent making FAssets less fungible, this request can be made by any address. - Self-closing can work with fractional lots, so it can be used to remove dust. - Liquidation can work with fractional lots too, so it can also be used to remove dust. ## Self-Minting Agents can also act as minters and mint FAssets from their own vaults. This process is called self-minting and is simpler than regular minting because neither the CRT nor the agent's fee are necessary. When an agent self-mints FAssets: - The agent still needs to pay the amount to mint on the underlying chain and execute the minting. - The self-minting operation also adds a [ticket to the redemption queue](#redemption-tickets-and-the-redemption-queue), alongside tickets added by mints done by other users. All tickets are processed by the FIFO queue. - Only the [pool's share of the fee](#fees) must be paid. Because self-minting is done without a collateral reservation request, in some cases, a change between the underlying deposit and the execution, such as another collateral reservation, price change which reduces the amount of free [lots](#lots), or lot-size change, might prohibit the intended number of lots to be minted. If one of these changes occurs, the agent can self-mint a smaller number of lots, even 0 lots, and the remainder of the deposited underlying assets is added to the free underlying balance. Additionally, when agents create a vault, they can choose not to make it public, so the vault can only be used to self-mint. :::tip[What's next] Learn more about the different components and processes involved in FAssets - [collateral](/fassets/collateral), [redemptions](/fassets/redemption), [liquidations](/fassets/liquidation) and [Core Vault](/fassets/core-vault). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## Direct Minting Direct minting allows users to mint FAssets by creating a single transaction on the underlying chain, bypassing the multi-step [collateral reservation process](/fassets/minting#minting-process) of standard minting. This feature is currently available only for XRP. ## How It Works The minter creates a payment transaction on the underlying chain (XRPL) to the [Core Vault](/fassets/core-vault) address, not to an individual agent's address. The transaction identifies the minting parameters (recipient and executor) through either a **destination tag** or a **memo field**. An **executor** then calls `executeDirectMinting` on the Flare side to finalize the mint. The executor is paid a fee upon successful completion. ## Fees Direct minting charges two fees, both deducted from the underlying payment amount: - **Minting fee**: A percentage of the received amount (in BIPS), with a minimum floor. This fee is paid to a governance-configured fee receiver. If the payment is smaller than the minimum minting fee, no FAssets are minted and the entire payment goes to the fee receiver. - **Executor fee**: A flat fee in the underlying asset, paid to the executor who finalizes the minting. The minting fee takes priority: if the payment only covers the minting fee, the executor receives nothing. ## Identifying Minting Parameters There are two ways to specify the minting recipient and executor in a direct minting payment. ### Destination Tag XRPL transactions natively support a **destination tag**, a 32-bit integer. The `MintingTagManager` contract on Flare acts as a registry that maps these destination tags to Flare-side parameters: the minting recipient and the preferred executor. The tag on the XRPL side and the tag registered in `MintingTagManager` on Flare are the same tag. The Flare contract gives the XRPL tag its meaning in the FAsset system. This path is convenient for repeated use, such as an exchange or service that regularly mints. ### Memo Field Instead of a tag, the minter can encode the minting parameters directly in the memo field on the XRPL payment. There are two supported formats: **32-byte memo** (recipient only): 1. **8-byte prefix**: `0x4642505266410018` (`DIRECT_MINTING`) 2. **4-byte zero padding**: `00000000` 3. **20-byte recipient address** Anyone can execute, as no executor is specified. **48-byte memo** (recipient and executor): 1. **8-byte prefix**: `0x4642505266410021` (`DIRECT_MINTING_EX`) 2. **20-byte recipient address** 3. **20-byte executor address** If the executor address is the zero address, anyone can execute. No reservation is needed for either format, but the memo must be constructed for each payment. ## Minting Tag Manager The `MintingTagManager` contract on Flare manages the reservation and ownership of XRPL destination tags for direct minting. - Users reserve destination tags by paying a reservation fee in native currency (FLR/SGB). The fee exists because the XRPL destination tag space is limited to 32-bit integers, and without a cost, someone could squat on all available tags. - Tags are assigned sequentially. The user always receives the next available tag. - The contract implements the **ERC-721** (NFT) interface, so reserved tags can be transferred or resold. - Tag owners can set a preferred executor with [`setAllowedExecutor`](/fassets/reference/IMintingTagManager#setallowedexecutor). Executor changes are not immediate: the new executor becomes active after a 10-minute cooldown to protect in-flight FDC proofs. - On initial reservation and tag transfer, the recipient resets to the new owner and the executor resets to the zero address. ## Executor Restrictions Executors can be restricted in three ways depending on the minting method: 1. **Tag-based:** The tag manager's [`setAllowedExecutor`](/fassets/reference/IMintingTagManager#setallowedexecutor) method defines the allowed executor. If set to the zero address, anyone can execute. 2. **Memo-based:** The 48-byte memo format encodes the executor address directly. The 32-byte memo format does not specify an executor, so anyone can execute. If the executor address in the 48-byte format is the zero address, anyone can also execute. 3. **Smart account:** The smart account manager may restrict the executor. The asset manager passes every request through. If the preferred executor does not act within `othersCanExecuteAfterSeconds`, anyone can execute the minting. ## Rate Limits Direct minting has multiple rate-limiting safeguards to protect against compromises, such as FDC exploits. | Limit | Description | | :------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------- | | [`directMintingHourlyLimitUBA`](/fassets/reference/IAssetManager#getdirectmintinghourlylimituba) | Hourly minting cap | | [`directMintingDailyLimitUBA`](/fassets/reference/IAssetManager#getdirectmintingdailylimituba) | Daily minting cap | | [`directMintingLargeMintingThresholdUBA`](/fassets/reference/IAssetManager#getdirectmintinglargemintingthresholduba) | Threshold above which a minting is considered "large" | | [`directMintingLargeMintingDelaySeconds`](/fassets/reference/IAssetManager#getdirectmintinglargemintingdelayseconds) | Automatic delay for large mintings | To inspect the live limiter and pre-flight a mint against the current windows, use the [Check Direct Minting Limits guide](/fassets/developer-guides/fassets-direct-minting-limits) along with [`getDirectMintingHourlyLimiterState`](/fassets/reference/IAssetManager#getdirectmintinghourlylimiterstate), [`getDirectMintingDailyLimiterState`](/fassets/reference/IAssetManager#getdirectmintingdailylimiterstate), and [`getDirectMintingsUnblockUntilTimestamp`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp). Rate limits **throttle** rather than reject. When limits are hit, further mintings are delayed proportionally to the accumulated backlog. Large mintings above the threshold are delayed independently by a fixed duration. Delayed mintings emit the `DirectMintingDelayed` event instead of `DirectMintingExecuted`, with an `executionAllowedAt` timestamp. Governance can unblock delayed mintings via `unblockDirectMintingsUntil`, but only for past timestamps and after manual review. If a preferred executor's minting is delayed, their exclusive execution window restarts from `executionAllowedAt`. ## Video Tutorial :::tip[What's next] To continue your FAssets development journey, you can: - Read the protocol details in [Direct Minting](/fassets/direct-minting). - Mint without a reserved tag using [memo-based direct minting](/fassets/developer-guides/fassets-direct-minting). - Check current limits and fees in [Operational Parameters](/fassets/operational-parameters). ::: --- ## Redemption Any holder of FAssets can redeem their FAssets for the underlying original asset. To do so, these holders, known as redeemers, send FAssets to the Asset Manager smart contract, and the redeemed amount is paid with the underlying asset from an agent's address. ## Redemption Process This is the summary of the redemption process: 1. The redeemer starts the redemption by issuing a request to the Asset Manager smart contract. The standard `redeem` method requires a whole number of lots, while `redeemAmount` and `redeemWithTag` allow redeeming any amount. The FAssets system chooses one or more redemption tickets from the front of the [FIFO redemption queue](/fassets/minting#redemption-tickets-and-the-redemption-queue). The number of chosen redemption tickets is capped to avoid high gas consumption. If the redemption amount requires too many tickets, only a partial redemption is done. 2. The system burns FAssets from the redeemer's account in the amount of the total of the selected redemption tickets. If the redeemer's account does not contain enough FAssets, the redemption fails immediately. 3. Each chosen ticket belongs to an agent. For every agent participating in the redemption, the system issues an event with the following redemption payment information: - Redeemer's underlying address. Agents can use the Flare Data Connector to ensure the validity of this address. Otherwise, malicious redeemers could provide an address that systematically blocks payments and exploits the redeeming process to their advantage. - Amount to pay minus the fee that was already subtracted. - [A payment reference](/fassets/minting#minting-payment-reference). This payment reference is different for each agent and each redemption. - The last underlying block and the last underlying timestamp to complete the payment. 4. Every agent pays the redeemer on the underlying chain and includes the payment reference in the memo field of the payment transaction. Agents can pay the redemption from their own address, which they control on the underlying chain. It does not need to be the same address where they receive minting payments. 5. After the payment is finalized, the agent uses the [FDC](/fdc/overview) to prove the payment and obtain a payment proof. 6. The agent (or, in Core Vault and specific flows, the **executor**) presents the payment proof to the FAssets system, which issues a **redemption ticket**. The executor role is responsible for ensuring that the system can finalize redemptions even if the agent is unresponsive or if the flow is managed by a central entity (such as the Core Vault executor). The redemption ticket is required to: - Prove the payment has occurred. - Trigger burning of the FAssets. - Release the corresponding agent's vault collateral and pool collateral. - Ensure the system tracks the underlying chain balances correctly. After the collateral is released, it can either back the minting of more FAssets or be withdrawn. ## Redeem Any Amount The standard `redeem` method requires whole lots. The `redeemAmount` method allows redeeming **any amount** of FAssets, which is useful for redeeming yields or partial amounts that don not align with lot boundaries. No redemption through `redeemAmount` or `redeemWithTag` can be smaller than `minimumRedeemAmountUBA` to prevent uneconomical micro-redemptions. If the requested amount requires too many redemption tickets, only a partial redemption is performed and the remaining amount is returned in a `RedemptionAmountIncomplete` event. ## Redeem with Tag The `redeemWithTag` method allows the redeemer to request an **XRP destination tag** on the redemption payment. This is useful when redeeming directly to an exchange address that requires a destination tag. Like `redeemAmount`, it also supports redeeming any amount, not just whole lots. The destination tag must fit in a 32-bit integer. This is an XRP-only feature, gated by the `redeemWithTagSupported` flag. Confirming a redemption with a tag requires the `confirmXRPRedemptionPayment` method, which uses a dedicated FDC proof type that supports destination tags. If the agent fails to pay, the redeemer calls `xrpRedemptionPaymentDefault` to trigger the default process. ## Fees These fees are charged when a user redeems FAssets for the underlying asset: | Fee Name | Charged In | Status | Purpose | | :----------------- | :------------------------------------ | :--------- | :---------------------------------------------------------------------------------------------------------------------------- | | **Redemption Fee** | Underlying Asset (e.g., XRP for FXRP) | Obligatory | Compensates the agent and collateral pool for providing the redemption service. | | **Executor Fee** | FLR | Optional | Compensates an optional executor who triggers redemption defaults when agents fail to pay, saving redeemers extra operations. | ### Redemption Fee The redemption fee is deducted from the amount of the underlying asset sent to the redeemer. The fee is split between the agent and the collateral pool: - **Agent**: Receives a portion of the fee as profit for providing the redemption service. - **Collateral Pool**: Receives the remaining portion of the fee, which is minted as new FAssets and deposited into the pool, increasing value for pool token holders. The fee percentage is defined in the operational parameters by governance and is the same for all agents, while the split ratio between the agent and the collateral pool is configured in each agent's settings. ### Executor Fee An optional executor fee can be paid to compensate an executor who triggers a redemption default if the agent fails to pay on the underlying chain. This spares the redeemer from having to perform extra operations to complete the redemption process. The executor fee is paid in FLR, and the amount is agreed upon off-chain between the redeemer and the executor. This fee is only applicable when an executor is involved in the redemption process. ## Redemption-Payment Failure Agents have a limited time to pay the redeemer on the underlying chain. The last block and the last timestamp on the underlying chain define the amount of time. If the payment is not made in time, the redeemer has to prove [payment non-existence](/fdc/attestation-types/referenced-payment-nonexistence) to be compensated. After the redeemer presents the payment non-existence proof, he is paid with the agent's collateral plus a _redemption default premium_. The premium is intended to encourage the agent to complete redemptions by paying with the underlying asset instead of collateral. If a payment fails and the failed transaction is recorded on the underlying chain, the agent must submit proof of the failed payment. In this way, the gas costs of the failed transaction can be accounted for by the FAssets system. If the transaction was not recorded, then no gas was spent, and reporting is not necessary. If the agent does not report the failed payment in time, anyone (including the executor) can report the failed payment and receive a reward from the agent's vault. :::info When payment fails because of the redeemer, the agent can obtain proof of the failed payment from the Flare Data Connector and present it to the FAssets system. The agent's obligation is then fulfilled, and he can keep both the collateral and the underlying. Two different proofs can be used: * Proof of invalid address, due to a wrong syntax or checksum, for example. * Proof of blocked payment: Even if the address is valid, it might contain a contract that blocks the payment. This can only occur on underlying networks that support smart contracts. The agent must still try to pay, and if the payment is blocked, the agent can request this proof from the Flare Data Connector and present it to the FAssets system. ::: During step 4 above, if any agent does not pay on the underlying chain, the redeemer completes the following procedure separately for each nonpaying agent: 1. The redeemer obtains a proof of payment non-existence from the Flare Data Connector. 2. The redeemer presents the payment non-existence proofs to the FAssets system, which triggers a redemption failure. 3. The redeemer is paid with collateral, according to the current price plus a premium. 4. FAssets are overcollateralized, so, even after paying the redeemer with a premium, a remainder is released. This remainder is derived from the [system-wide collateral ratio settings](/fassets/collateral#system-wide-thresholds) specified by governance. 5. The underlying assets backing the redeemed FAssets are marked as free and can be withdrawn by the agent later. ## Redemption Time Extension When many redemption requests target the same agent in rapid succession, the system automatically extends the agent's payment deadline. Each new redemption request adds time to the deadline, which then gradually decays back to the baseline as the volume of requests decreases. This mechanism prevents agents from being overwhelmed by a burst of redemptions and failing to pay in time through no fault of their own. ## Edge Cases ### Unresponsive redeemer After a redemption nonpayment, the redeemer might not report the failure for some reason. In this case, the agent or the executor can present a payment non-existence proof, and the redeemer receives collateral plus a premium. After this operation, the underlying backing collateral and the remaining local collateral are released. ### Unresponsive agent After a successful payment, the agent might not present the payment proof (redemption ticket). Because the agent has already paid, the redeemer is not affected. However, the system still requires the payment proof to track the agent's balance on the underlying chain correctly. After enough time for the agent to present the proof has elapsed, anyone, including the executor, can show the payment proof and receive collateral from the agent's vault as a reward. ### Expired proof Proofs provided by the [Flare Data Connector](/fdc/overview) can be created for 14 days and remain valid indefinitely once created. If neither the redeemer, the agent, nor the executor presents proof of payment or evidence of non-payment within the 14-day window, the regular redemption process cannot continue, and the agent's collateral could be locked indefinitely. The procedure to recover this collateral is the same as the procedure in the minting case. ## Self-redemption Agents can also act as users and redeem FAssets from their own vaults. This process is called self-redemption or self-closing, and it is simplified because payment on the underlying chain is not required. As shown in the following process, agents can self-redeem for any reason, including to prevent liquidations, as it reduces the amount of FAssets the agent is backing. 1. An agent sends FAssets to their account. 2. FAssets are burned. 3. The collateral that was backing those assets is released. 4. The underlying collateral is released and can be withdrawn from the underlying address later. The self-redeemed amount is not limited to a positive integer of lots and can be less than one lot, which makes self-closing ideal for redeeming an agent's dust. :::tip[What's next] Learn more about the different components and processes involved in FAssets - [collateral](/fassets/collateral), [minting](/fassets/minting), [liquidations](/fassets/liquidation), and [Core Vault](/fassets/core-vault). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## Collateral FAssets collateral is locked in contracts that ensure the minted FAssets can always be redeemed for the underlying assets they represent or compensated by collateral. Along with Flare's native token, FLR, any governance approved ERC-20 token on the Flare blockchain can be used as collateral. FAssets collateral ensures the security and redemption of minted FAssets by locking collateral in smart contracts. This guarantees that FAssets can either be redeemed for their underlying assets or compensated by collateral. Collateral can include Flare's native token (FLR) and any governance-approved ERC-20 tokens on the Flare blockchain. ## Collateral Types Two primary types of collateral secure FAssets: **Vault Collateral** and **Pool Collateral**. Vault collateral is provided exclusively by agents and ensures they perform their duties. Pool collateral is provided by agents and FLR holders who choose to contribute to the pool. It is a safeguard when a sudden drop in the price of the vault collateral makes it insufficient to back the underlying assets. ### Vault Collateral Vault collateral consists of the types of collateral chosen by agents to store in their vault. Flare governance approves the valid types, which are generally stablecoins, such as USDC, USDT, or other highly liquid tokens on the Flare network. Agents choose one of the types defined by FAssets governance and use it as collateral in their vaults. Agents cannot switch to a different type after a vault is created, but they can create any number of vaults, with different types. Each collateral type defines an ERC-20 token to use as collateral, a series of [collateral ratios](#collateral-ratio), and information to retrieve the asset's price from the FTSO system. Governance reserves the right to add new types or deprecate existing types. If governance deprecates a type, agents must switch to a supported type. Each vault is associated with a single, unique address on the underlying chain called the agent's underlying address. It receives underlying assets when they are minted into FAssets and sends underlying assets to the redeemer's address when they are redeemed. When an agent creates a vault, the underlying address is checked for validity using the Flare Data Connector. Otherwise, malicious agents could provide an address that systematically blocks payments and exploit the [minting process](/fassets/minting) to their advantage. ### Pool Collateral When the price of the vault collateral changes in such a way that the vault collateral cannot fully back all the minted FAssets, a [liquidation](/fassets/liquidation) mechanism ensures enough FAssets are burned to restore balance. The pool collateral provides an additional source of backing for situations when the price fluctuates too rapidly for liquidations to correct the imbalance. Pool collateral is always native FLR tokens or SGB tokens on the Songbird network and can be used as an additional source of collateral for [liquidations](/fassets/liquidation) and [failed redemptions](/fassets/redemption#redemption-payment-failure). Anyone can participate in the FAssets system by providing native tokens to this pool. In return, providers receive **collateral pool tokens** (CPTs) as proof of the share of native tokens they provided to a specific pool from a specific agent. CPTs are ERC-20 tokens specific to both an agent and a pool. Providers can redeem their CPTs for FLR, or even transfer or trade them, after a governance-defined time period has elapsed since they entered the pool. This **time lock** is necessary to reduce sandwiching attacks. Additionally, CPT holders are entitled to a share of any fee the agent earns from minting FAssets using this pool as explained in the next section.
CPT conversion formulae and examples. The amount of collateral pool tokens a provider $p$ receives upon entering a pool is calculated as: $$ CPT_{p, rec} = \frac{C_{p}}{C_{total}} \times CPT_{iss} $$ where: - $C_{p}$ is the amount of FLR tokens that provider $p$ is adding to the pool. - $C_{total}$ is the amount of FLR tokens in the pool before adding the new tokens. - $CPT_{iss}$ is the circulating amount of collateral pool tokens. When a pool is first created, $CPT_{p, rec} = C_{p}$. The amount of FLR collateral a provider receives when they redeem their CPT is calculated using the opposite formula: $$ CPT_{p, rec} = C_{total} \times { CPT_{p, red} \over CPT_{iss} } $$ where: - $CPT_{p, red}$ is the amount of CPT the provider is returning to the pool. --- The following example shows conversion of CPTs. | | FLR in pool | Issued CPTs | | ------------------------------------------------------------------------------------- | ----------: | ----------: | | An agent creates a new vault. The collateral pool is initially empty of FLR and fees. | 0 | 0 | | Alice deposits 100 FLR and gets 100 CPTs in return. | 100 | 100 | | Bob deposits 200 FLR and gets 200 CPTs in return. | 300 | 300 | | Alice redeems 50 CPTs and receives 50 FLR in return. | 250 | 250 | Note that in general 1 FLR does not always correspond to 1 CPT, because of mechanisms like the [top-up](#top-up), for example.
## Collateral Ratio The collateral ratio (CR) is the ratio between the value of all the tokens used as collateral and the total value of the underlying assets held by an agent at any given time. The agent's vault and the collateral pool each has its own unique collateral ratio, which is constantly changing as the value of the underlying assets and the collateral change. These values are obtained using the [FTSO](/ftso/overview). The following example shows vault and pool CR: :::info[Vault and pool CR] Assume an amount of FAssets currently valued at \$1000 USD, backed by \$1500 worth of USDC in vault collateral and \$2000 worth of FLR in pool collateral. The resulting vault CR is: $$ \frac{\text{\$1500}}{\text{\$1000}} = 1.5 $$ The resulting pool CR is: $$ \frac{\text{\$2000}}{\text{\$1000}} = 2 $$ ::: Several thresholds are defined for the collateral ratio, and they are used at different times during the FAsset operations. Some are set by the system, and others are set by the agent: ### System-Wide Thresholds The following thresholds are set by the FAssets system's governance and are the same for all agents. #### Minimal CR The lowest collateral ratio the agent vault and the collateral pool must maintain so that enough collateral exists to insure the minted FAssets and to compensate for redemption payments that fail. The minimal CR can be different for each type of collateral. If an agent's CR remains below the minimal CR for longer than a governance-set amount of time, [liquidations](/fassets/liquidation) can start. #### Liquidation CR **Liquidation CR**: An agent's position is unhealthy when the agent's vault CR or pool CR fall below their minimal CR. However, as long as the CR remains above liquidation CR, the CR can briefly fall below the minimal CR. During this time, the agent can either deposit more collateral or self-close some backed FAssets to improve the position. However, if the CR falls below the liquidation CR, liquidations can start immediately. The value of each liquidation CR is approximately 10% less than the minimal CR. :::info[Example liquidation CR] Assume the **minimal CR** is 1.4 and the **liquidation CR** is 1.3. If the agent's vault CR drops below 1.3, the agent's position can be liquidated immediately. If the agent's vault CR drops below 1.4 but not below 1.3, the agent has some time to amend the position before it can be liquidated. Adjusted for the collateral pool's minimal CR, the same example applies to the collateral pool. ::: #### Safety CR If one or both of the collateral types fall below liquidation CR or below the minimum CR for a longer period of time, liquidation occurs. When the offending collateral reaches a healthy CR again, the liquidation stops. To prevent the agent from immediately reverting into liquidation after a small price change, the CR must reach the safety CR before it can start operating normally again and liquidation stops. Each of the collateral types, the agent's vault and the collateral pool, has its own unique safety CR. ### Agent Thresholds The following thresholds are set by each agent according to their own preferences. #### Minting CR For each mint done by an agent, the maximum amount allowed to be minted is calculated so that the CR for the agent's vault and the CR for the agent's collateral pool after the mint remain higher than the minting CR for each collateral type. To reduce the threat of liquidation, agents should set the minting CR well above the minimal CR to accommodate price fluctuations that might occur before the CR falls below the minimal CR after the mint and minting is no longer possible. #### Exit CR After a user redeems CPTs, the pool CR must be more than the exit CR. If the pool CR is already below the exit CR, redemption cannot occur. The exit CR is for the collateral pool only. #### Top-up CR To incentivize healthy collateral pools, if the pool CR falls below the top-up CR, anyone can add collateral to the pool and receive [CPTs](#pool-collateral) at a reduced price. This [top-up mechanism](#top-up) decreases the likelihood of liquidations because of a low amount of pool collateral. ## Minting Fees and Debt As part of the minting process, users pay a [minting fee](/fassets/minting#fees) on the underlying chain. The agent's share of this fee remains on the underlying chain, whereas the pool's share triggers the minting of an equivalent amount of FAssets on the Flare network. These FAssets coming from the minting fee are added to the collateral pool, where they are shared between collateral providers in proportion to the amount of CPTs that providers have. At any time, providers can claim their due share of the fees in the pool. When providers exit the collateral pool, they must first call the [`withdrawFees`](/fassets/reference/ICollateralPool#withdrawfees) to claim their remaining unclaimed fees. Only then can they redeem their CPTs to exit the pool. Providers are naturally only entitled to the minting fees accrued after they entered the pool. Therefore, providers entering a pool with preexisting fees are assigned a **fee debt**. The amount of fees a provider can actually withdraw from the pool is calculated by first subtracting their debt from the total amount of fees in the pool. In this way, the amount of fees that a provider can withdraw upon entering a pool is exactly zero. A provider's fee debt: | Increases when the provider | Decreases when the provider | | ------------------------------------------- | ---------------------------------------------- | | Enters a pool which already has fees in it. | Exits the pool, partially or completely. | | Withdraws FAsset fees. | Deposits FAssets, paying off part of the debt. | It is worth noting that: - When a provider withdraws fees, their debt increases by the same amount. - Since CPTs are ERC-20 tokens, a secondary market for them is expected to develop. If CPTs become more valuable than the FAsset fees they represent, returning the FAssets and paying off part of their fee debt might be more lucrative for providers.
Fee entitlement formulae and examples. The following formulas are based on the concept of virtual fees, which are the fees that a provider would be entitled to if they had no fee debt. The amount of debt a provider $p$ is assigned upon entering a pool is calculated as: $$ fee\_debt_p = { added\_collateral_p \over collateral\_in\_pool } \times total\_virtual\_fees $$ where: - $added\_collateral_p$ is the amount of FLR tokens that provider $p$ is adding to the pool. - $collateral\_in\_pool$ is the amount of FLR tokens in the pool before adding the new tokens. - $total\_virtual\_fees$ is the sum of all provider's virtual fees. The following formulas use **total virtual fees**, which are the gross fees accrued to a provider assuming no prior fee withdrawals, slashing, or outstanding fee debt. The **total virtual fees** is the sum of all provider's virtual fees and can be expressed as: $$ total\_virtual\_fees = fees\_in\_pool + total\_fee\_debt $$ where: - $total\_fee\_debt$ is the sum of the fee debt held by all providers $= \sum_p fee\_debt_p$ Then, the virtual fees due to a provider $p$, i.e., the amount of FAsset minting fees they would be entitled to if they had no debt, are: $$ virtual\_fees_p = { CPT_p \over currently\_issued\_CPT } \times total\_virtual\_fees $$ where: - $CPT_p$ is the amount of CPTs provider $p$ holds. - $currently\_issued\_CPT$ is the circulating amount of CPTs. Finally, the amount of fees from the pool that a provider $p$ is free to withdraw at any given time is: $$ free\_fees_p = virtual\_fees_p - fee\_debt_p $$ where: - $fee\_debt_p$ is the amount of fee debt that provider $p$ holds. --- The following example shows fee entitlement. | | FLR in pool | Fees in pool | Total fee debt | Total virtual fees | | --------------------------------------------------------------------- | ----------: | -----------: | -------------: | -----------------: | | An agent creates a new vault. | 0 | 0 | 0 | 0 | | Alice deposits 100 FLR and gets 100 CPTs in return. | **100** | 0 | 0 | 0 | | 10 FAssets of fees are added to the pool due to a mint. | 100 | **10** | 0 | **10** | | Bob deposits 100 FLR and gets 100 CPTs in return. | **200** | 10 | **10** | **20** | | 10 more FAssets of fees are added to the pool due to another mint. | 200 | **20** | 10 | **30** | | Alice withdraws 10 FAssets of fees. | 200 | **10** | **20** | 30 | | Bob exits the pool by returning the 100 CPTs and withdrawing 100 FLR. | **100** | **5** | **10** | **15** | After step **4**, Bob is not entitled to any of the fees in the pool: - Bob is assigned an initial fee debt of 10 FAssets, according to the $fee\_debt_p$ formula in the box above. - As a result, the total virtual fees are increased to 20 FAssets. 10 of them are in fees, and 10 of them are in debt. - Each user now holds half the total CPTs, therefore they are allowed to withdraw half the virtual fees, this is, 10 FAssets each. - Alice has no debt, so she can withdraw 10 FAssets, which is all the fees in the pool, because she was the only CPT holder when these fees were accrued. - Conversely, Bob has 10 FAssets of debt, so he can't withdraw any of the fees. After step **5**, the new fees are shared between both users, and the previous 10 FAssets still belong to Alice: - The 10 FAssets in new fees increase the total virtual fees to 30. - Both users are entitled to half of the total, which is 15 FAssets each. - Alice has no debt, so she can withdraw 15 FAssets: the initial 10 plus half of the 10 that were added to the pool afterwards. - Bob has 10 FAssets of debt, so he can only withdraw 5, this is, his entitlement (15) minus the debt (10). After step **6**: - The 10 FAssets that Alice has withdrawn have converted into debt for her. - However, this action does not change the total virtual fees because the sum of fees in the pool and total debt remains constant. - Therefore, both users are still entitled to 15 FAssets each. - However, now Alice has 10 FAssets of debt, so she can withdraw only 5 more. - Nothing has changed for Bob, who can still withdraw 5 FAssets. In step **7**: - Bob is returning 100 CPTs, which is 50% of the circulating CPTs, so he is entitled to half the total virtual fees, 15 FAssets. - Because he is exiting the pool, all his debt, which is 10 FAssets, must be cancelled. - He can withdraw the remaining 5 FAssets from the fees pool. - After Bob withdraws his 5 FAssets, the pool contains only 5 FAssets, which correspond to the amount that Alice can withdraw.
## Transferable and Locked CPTs CPTs can always be **redeemed** by exiting the pool, but only the portion above the fee debt can be **transferred** to another account; therefore, CPTs held by providers are divided into two types. ### Transferable Tokens whose time lock has expired and are also free of fee debt. These tokens are fungible, and they can be transferred or traded just like any other ERC-20 token. ### Locked The CPTs serve only as proof of ownership of some of the collateral in the pool, and they cannot be transferred nor traded. Locked CPTs are one of the following types: - **Time-locked**: Tokens whose time lock has not expired must wait to become transferable or redeemable. - **Debt-locked**: Tokens corresponding to an amount of fees below the provider's fee debt cannot be transferred because they would need to carry the debt with them. However, they can be [redeemed](#cpt-redemption). As new fees arrive in the pool, some previously debt-locked tokens become transferable. These CPTs can also become transferable by adding FAssets to the pool, which settles, either partially or completely, the fee debt.
CPT transferability formulae and examples. The amount of CPTs that a given provider $p$ can transfer is calculated as: $$ transferable\_CPT_p = { free\_fees_p \over virtual\_fees_p } \times CPT_p $$ where: - $free\_fees_p$ is the amount of fees from the pool that a provider $p$ can withdraw, as defined in the previous formula box. - $virtual\_fees_p$ is the amount of FAsset minting fees that provider $p$ would be entitled to if they had no debt, as defined in the previous formula box. - $CPT_p$ is the amount of CPTs provider $p$ holds. Accordingly, the amount of CPT that is locked and cannot be transferred is calculated as: $$ locked\_CPT_p = { fee\_debt_p \over virtual\_fees_p } \times CPT_p $$ As new minting fees arrive in the pool, the $transferable\_CPT_p$ of all providers also increases. Conversely, when a provider withdraws fees from the pool, their debt increases in the same amount, and $total\_virtual\_fee$ remains the same. Therefore, only that provider's $transferable\_CPT_p$ is reduced, without affecting the rest of the providers. --- The following example shows transferability of CPTs | | Issued CPTs | Fees in pool | Total fee debt | Total virtual fees | | ------------------------------------------------------------------ | ----------: | -----------: | -------------: | -----------------: | | An agent creates a new vault. | 0 | 0 | 0 | 0 | | Alice deposits 100 FLR and receives 100 CPTs. | **100** | 0 | 0 | 0 | | 10 FAssets of fees are added to the pool due to a mint. | 100 | **10** | 0 | **10** | | Alice withdraws 5 FAssets of fees. | 100 | **5** | **5** | 10 | | 10 more FAssets of fees are added to the pool due to another mint. | 100 | **15** | 5 | **20** | | Alice transfers 75 CPTs to Bob. | 100 | 15 | 5 | 20 | | Alice exits the pool by returning her remaining 25 CPTs. | **75** | 15 | **0** | **15** | After step **2**, all of Alice's CPTs are transferable because she has no debt. After step **3**, all of Alice's CPTs continue to be transferable, and she is entitled to 100% of the fees in the pool. If she transferred or traded his CPTs, the recipient of those CPTs would be entitled to the fees. After step **4**, only half of Alice's CPTs are transferable (50 CPTs). The other half is debt-locked. After step **5**, only 25% of Alice's CPTs remain locked (25 CPTs), which correspond to her 5 FAssets of debt. After step **6**: - Alice has 25 CPTs, which entitle her to 5 FAssets of virtual fees. After subtracting her 5 FAssets of fees, her free fees are zero, which means she cannot withdraw any more fees. - Bob has 75 CPTs and no debt, so he is entitled to 15 FAssets of fees, which are all the fees in the pool. In step **7**: - Alice is returning 25 CPTs, which is 25% of the circulating CPTs, so she is entitled to 25% of the total virtual fees, which is 5 FAssets. - Because she is exiting the pool, all her debt, which is 5 FAssets, must be cancelled. - Her $free\_fees_p$ are 0, so she cannot take any of the remaining fees in the pool. - The 15 FAssets that remain in the fee pool now belong entirely to Bob, who holds 100% of all the issued CPTs, which is 75 CPTs.
## CPT Redemption When collateral providers exit the pool by redeeming their CPTs, the FAssets system burns them and returns the appropriate share of the collateral plus the share of [FAsset-minting fees minus any FAsset-fee debt](#minting-fees-and-debt). Providers also have the option to exit the pool partially, by redeeming only some of their CPTs. In this case, they can choose one of the following options to manage their due FAsset fees: withdraw the fees, reduce the fee debt, or both, keeping the current fee-to-debt-ratio. However, providers can exit, either fully or partially, only when the [collateral ratio CR](#collateral-ratio) is high enough. After they exit, the **CR** must be higher than the **exit CR** to prevent their exit from reducing the **CR** to a dangerous level. Therefore, exits are impossible when the **CR** is below the **exit CR**. In this case, if providers have enough FAssets, they can exit by **self-closing**, which burns enough of their FAssets, plus their fees, to release their collateral. Providers are mainly compensated in underlying assets for the burned FAssets, depending on the [number of lots](/fassets/minting#lots) of FAssets that need to be redeemed: - If more than 1 lot needs to be redeemed, the value of the burned FAssets is redeemed through the standard [redemption process](/fassets/redemption). - If less than 1 lot needs to be redeemed, the agent buys the underlying funds from the user using vault collateral, at the price reported by the [FTSO](/ftso/overview) minus a percentage defined by the agent. This purchase by the agent occurs because fees on underlying chains can be expensive, which makes redemption of small quantities too expensive for the agent. Providers can always request this option instead of receiving underlying tokens. Also, if enough vault collateral is not available, pool collateral is used instead. :::warning In the case where the agent does not redeem in the underlying asset, the FAssets system pays the provider in collateral from the agent's vault because the pool collateral backing the redeemed FAssets is already withdrawn. When this type of redemption occurs, users might receive less collateral than they would have received if they had made a normal redemption. ::: ## Agent Stake Agents must have a stake in their collateral pools, which means they must hold the amount of CPTs proportional, by a system-defined constant, to the backed amount of FAssets. The maximum amount of minting is limited by the amount of collateral pool tokens held by the agents. The agents' tokens are locked, which means they cannot be redeemed or transferred, while agents back these FAssets. When the agent's portion of the collateral pool is below the threshold, new mintings are not allowed. However, this situation does not trigger a liquidation because only the total pool stake matters when collateral needs to be redeemed or a liquidation payment needs to be made. If an agent's actions force a payment to be made from the collateral pool, the agent's CPTs, valued by the paid native tokens and recalculated by the collateral-pool-price formula, are burned. These actions can cause the agent's CPTs to be burned: - When a redemption payment fails, when enough vault collateral to compensate the redeemer is not available, or when the system is set to automatically pay for redemption failures from the collateral pool. - Liquidation because the CR of the vault collateral is too low. - Full liquidation because of an [agent infraction](/fassets/redemption#redemption-payment-failure) during a transfer on an underlying chain. ## Top-up To reduce the likelihood of liquidations because the pool collateral is too low, the pool can be topped up at a reduced price when the **CR** is above the **top-up CR**. A top-up mechanism for vault collateral is not available. To prevent liquidation, agents can add vault collateral any time. :::tip[What's next] Learn more about the different components and processes involved in FAssets - [minting](/fassets/minting), [redemptions](/fassets/redemption), [liquidations](/fassets/liquidation) and [Core Vault](/fassets/core-vault). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## Core Vault ## Overview The **Core Vault (CV)** is a specialized FAsset system vault that operates on the underlying network. It addresses a critical challenge in cross-chain systems: **protecting user funds from malicious agents while maintaining system scalability.** ### Why the Core Vault? - In **FAssets v1**, participating agents were required to individually hold sufficient redemption assets (e.g., XRP) in their wallets. - To prevent theft, agents needed to be heavily overcollateralized - they had to lock up more value than they could ever profit from stealing. - This worked, but it limited minting capacity and made the system capital-inefficient. The **Core Vault** changes this model: - Instead of personally holding redemption assets (e.g., XRP), participating agents can now utilize a shared, insured, non-custodial vault. - The vault is **multisig-controlled** and governed by Flare, so agents cannot unilaterally take the funds. - Because theft is structurally prevented, agents no longer need extreme levels of overcollateralization. The Core Vault prevents agents from running away with XRP while simultaneously lowering collateral requirements and improving system scalability. ### Key Properties - **Secure by design**: Agents cannot withdraw underlying assets directly. - **Capital efficient**: Agents can mint more FAssets with less collateral. - **System-wide liquidity**: Assets in the CV form a shared pool available to all agents. - **Governance oversight**: Multisig with pause controls and time-bounded fund release. Introduced in **FAssets v1.1**, the Core Vault enables the system to expand its minting capacity while maintaining the same strong guarantees of safety for users. ## Key Features ### Transfer Capacity To ensure redemption liquidity, a parameter enforces that after transferring assets to the Core Vault, **an agent must still maintain a minimum portion of minting capacity outside the CV**. This prevents agents from moving everything into the vault and leaving the system unbalanced. ### Security Design - Each supported asset (XRP, BTC, DOGE, etc.) has its **own dedicated Core Vault**. - Each CV is a **multisig account on the underlying chain**, operated under a formal governance agreement with Flare. - Funds in the CV **no longer belong to a single agent** - they are pooled, with withdrawals only allowed under system rules. - Governance can pause deposits or withdrawals if suspicious activity is detected. ## Core Vault Implementation ### Fund Movement on Underlying Networks The Core Vault operates differently depending on the underlying network: **For XRP Ledger (XRP CV):** - The CV is implemented as a **multisig account** on the XRP Ledger. - Only the authorized **multisig signers** can move XRP from the vault. - These signers are authorized by Flare governance and operate under formal agreements. - All outgoing transactions require multiple signatures from the authorized signers. ### Agent Ownership vs. Core Vault Ownership There's an important distinction between agent ownership and CV ownership: #### Agent Ownership (Standard FAssets) - Agents hold underlying assets in their own wallets. - These assets belong to the specific agent and are part of their collateral. - Agents have direct control over these assets. #### Core Vault Ownership - Assets transferred to the CV become part of a shared pool. - These assets **do not belong to any specific agent** once in the CV. - The CV is a system-level reserve that any agent can request assets from. - This allows for better capital efficiency and system-wide liquidity. ### Agent Collateral Requirements Agents are required to provide collateral in the form of: - **Flare's native token (FLR or SGB)** - **USDC/USDT (stablecoins)** When agents transfer underlying assets to the CV, they can reduce their collateral requirements while maintaining their minting capacity. The CV effectively acts as an **insurance-backed reserve** that boosts efficiency without weakening security. ## Operational Workflow ### Transferring to Core Vault 1. **Agent Transfers Assets** The agent announces a transfer and sends the underlying asset (e.g., XRP) to the Core Vault (CV) address with a valid payment reference. 2. **Proof of Payment Submitted** Anyone (including the agent) submits the proof of the transfer to the FAsset system. 3. **Verification and Redemption** After verifying the payment, the system releases the agent's collateral. :::info Transfer requests do not expire. Agents must either complete the transfer or cancel and re-queue it. Inaction leaves collateral locked indefinitely. ::: ### Redemption from Core Vault There are two methods to retrieve assets from the CV: #### Request for Return (Agents Only) An agent can request assets from the CV through a special minting process, which creates a collateral reservation. Once the request is made, CV operators execute the transfer and submit proof of payment to the asset manager. There is no time limit for the CV to honor the request, but governance ensures timely execution. #### Direct Redemption (Users) Approved users can burn FXRP and receive underlying XRP from the CV. This requires KYC approval and a minimum redemption threshold. It is typically processed once per day and has a lower priority than agent return requests. It is helpful for large, less time-sensitive redemptions. ## XRP Core Vault Design The XRP Core Vault is a multisig account on the XRP Ledger, backed by an audited Flare smart contract that emits transaction instructions for execution. ### Transactions in XRP Core Vault The XRP Core Vault (CV) uses two types of transactions: - `Payment` [Transactions](https://xrpl.org/docs/references/protocol/transactions/types/payment): Transfers XRP back to agents upon redemption. - `EscrowCreate` [Transactions](https://xrpl.org/docs/references/protocol/transactions/types/escrowcreate): Time-locks XRP to control fund releases and minimize spending risk. :::info The vault is operated manually. Multisig signers validate all outgoing transactions against a pre-approved rule set and sign them only during designated daily windows. ::: ### Daily Security Routine 1. **Escrow Expiry Adds Funds** An expired escrow (size L) adds XRP to the vault's available pool. 2. **Withdrawals & Payouts** Users request withdrawals; multisig operators validate Flare's off-chain instructions and process payouts. 3. **Re-escrow Excess & Maintain Reserve** Remaining funds are escrowed in batches (size L), while a minimum reserve (M) is kept in the vault. Three escrows are created if `remaining_funds = M + 3L`, and M stays in the wallet. ### Security Model The XRP Core Vault features enhanced security measures for managing daily liquidity, including escrow time-locking and a minimum reserve in multisig setups. It has an emergency pause function and "Red Alert Mode" for urgent threats. In case of a security issue, all signing is halted pending governance review. Escrow accounts may be unlocked to prevent asset loss, with governance overseeing the restoration of operations. Only one escrow can be unlocked per day, minimizing potential damage. ## Summary The **Core Vault** fundamentally strengthens FAssets: - **Prevents loss of underlying assets** by removing agent control over pooled funds. - **Lowers collateral burdens**, improving capital efficiency. - **Expands minting capacity**, making the system more scalable. - **Adds layered security**, combining multisig, escrow, and governance controls. It is the key to making cross-chain assets on Flare both **secure** and **efficient**. :::tip[What's next] Learn more about the different components and processes involved in FAssets - [collateral](/fassets/collateral), [minting](/fassets/minting), [redemptions](/fassets/redemption) and [liquidations](/fassets/liquidation). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## Liquidation Liquidation is the process of selling assets to bring the FAssets system back to health after an [agent](/fassets/overview#agents) becomes undercollateralized. The following types of liquidation can occur: - **Unhealthy position liquidation**: Occurs when the [collateral ratio (CR)](/fassets/collateral#collateral-ratio) of either the agent's vault or collateral pool falls below its respective [minimal CR](/fassets/collateral#system-wide-thresholds). In this case, the agent's position is liquidated until the collateral ratio reaches the [safety CR](/fassets/collateral#system-wide-thresholds) or all of the backed FAssets are liquidated. - **Full liquidation**: Occurs when the agent makes an [illegal payment](#illegal-payments) from the underlying chain address. In this case, all the FAssets backed by the agent are liquidated, and the liquidation cannot be stopped. In both cases, [liquidators](/fassets/overview#liquidators), who can be anyone who holds FAssets, are encouraged to sell their FAssets back to the system. They will be paid with the agent's collateral plus a premium, as a penalty against the agent for unhealthy positions or misconduct. ## Liquidation Process When liquidation starts, any liquidator can send FAssets and get paid with a combination of vault collateral and pool collateral at the current asset price multiplied by a premium factor greater than 1. The maximum amount of FAssets that is accepted is the amount required to make the agent's position healthy again, rounded up to the next lot. The premium is a system-defined percentage, and it can increase through the duration of the liquidation. The premium is limited to the agent's combined collateral ratio, which is the sum of the current value of the vault collateral and pool collateral divided by the current value of the backed FAsset amount. However, if this limit is reached, all the agent's backed FAssets are liquidated, and all the vault collateral and pool collateral are paid to the liquidators. The liquidation-collateral payment is divided between the agent and the collateral pool. A fixed ratio (≥ 1.0) of the payment is paid from the agent's collateral, and the remainder is paid from the pool collateral. If not enough of one type of collateral exists, more is paid from the other type. :::info Illegal payments trigger a full liquidation, which involves the following additional actions: * The liquidated agent's vault is locked so that it cannot be used to mint again. If the agent wants to continue to mint FAssets, he must create a new agent vault with a new underlying address. * Ongoing mintings against this agent's locked vault continue, but the minted FAssets are immediately added to the liquidation process. * Ongoing redemptions continue. New redemptions can start until all the agent's redemption tickets are liquidated. Unfortunately, if the agent's underlying backing is unhealthy, redeemers are more likely to be paid in native tokens from the collateral pool. This liquidation process includes the time-increasing premium, and it only stops when all the agent's collateral is liquidated. ::: ## Stopping Liquidations After liquidation of an unhealthy position starts, it can be stopped by depositing enough collateral or self-closing FAssets to reach the [safety CR](/fassets/collateral#system-wide-thresholds). Also, if a change in the price pushes the CR above the [safety CR](/fassets/collateral#system-wide-thresholds), anyone can stop the liquidation by notifying the FAssets system. To maintain a healthy account, agents should track positions and automatically top up or self-close FAssets when liquidation approaches. Otherwise, the agent and the liquidators compete to try to stop the liquidation. To stop a liquidation, the agent's vault must reach the [safety CR](/fassets/collateral#system-wide-thresholds), which is above the minimal CR that triggered the liquidation. The top-up mechanism can prevent liquidations caused by a low CR in the collateral pool, but full liquidations cannot be stopped. However, an agent can still self-close positions to avoid paying a premium to liquidators.
Example with small price movement. Using BTC as underlying and USDC as collateral, an agent creates a vault to mint FBTC FAssets. 1. Initial conditions: - The agent is backing 1 FBTC, currently valued at $20K, according to the FTSO system. - The minimal CR is **1.3** for the vault collateral and **2.5** for pool collateral. - The agent must hold 20% of the pool's minimal CR. In this case, 20% of 2.5 times \$20K is **\$10K**. - The underlying backing factor is 50%, so the agent needs to hold only **0.5** BTC. - The liquidation premium factor is 1.1, of which 1.0 is paid in vault collateral, and 0.1 is paid in pool collateral. At this point, the 1 FBTC is backed by: - 0.5 BTC underlying. - $26K worth of USDC vault collateral. The vault CR is $$ \frac{\text{\$26K}}{\text{\$20K}} = 1.3 $$, equal to the vault's minimal CR. - \$60K worth of FLR in pool collateral, of which $10K belongs to the agent. The pool CR is $$ \$60K \over \$20K $$ \= 3, above the pool's minimal CR. 2. Now the price of BTC increases from \$20K to $21K. As a result: - The vault CR is $$\frac{\text{\$26K}}{\text{\$21K}} \approx 1.24$$, **below the vault's 1.3 minimal CR**. - The pool CR is $$\frac{\text{\$60K}}{\text{\$21K}} \approx 2.86$$, still above the pool's 2.5 minimal CR. :::warning Because one of the CRs is below the minimal CR, liquidation can start after a system-defined wait period. If any of the CRs go below the liquidation CR, liquidations can start immediately. ::: 3. A liquidator notices the CR levels and decides to liquidate $10K worth of FAssets by returning 0.48 FBTC to the FAssets system. The liquidation premium factor is 1.1, so the liquidator receives $11K worth of assets: - $10K worth of USDC from the agent's vault collateral. - $1K worth of FLR from the agent's portion of the collateral pool. The corresponding $1K worth of CPTs are burned, so their price is unaffected. At this point, the agent is backing 0.52 FBTC with: - 0.5 BTC underlying. The ratio is $$ \frac{0.5}{0.52} \approx 0.96 $$, well above the 50% underlying backing factor. - $16K worth of USDC vault collateral. The vault CR is $$ \frac{\text{\$16K}}{\text{\$11K}} \approx 1.45 $$, now above the vault's minimal CR. - \$59K worth of FLR in pool collateral, of which $9K belong to the agent. The pool CR is $$ \frac{\text{\$59K}}{\text{\$11K}} \approx 5.36 $$, still well above the pool's minimal CR. Both CRs are now above the minimal CR values, but liquidation does not stop until the CRs further increase up to the safety CR. In summary, as a result of the price increase and the liquidation, around 50% of the backed FBTC was burned. The actual amount of FAssets that need to be burned, though, depends on the safety CR setting.
Example with large price movement. The same setup and initial conditions as in Example 1 are used: Using BTC as underlying and USDC as collateral, an agent creates a vault to mint FBTC FAssets. 1. Initial conditions: - The agent is backing 1 FBTC, currently valued at $20K, according to the FTSO system. - The minimal CR is **1.3** for the vault collateral and **2.5** for pool collateral. - The agent must hold 20% of the pool's minimal CR. In this case, 20% of 2.5 times \$20 K is **\$10 K**. - The underlying backing factor is 50%, so the agent needs to hold only **0.5** BTC. - The liquidation premium factor is 1.1, of which 1.0 is paid in vault collateral, and 0.1 is paid in pool collateral. At this point, the 1 FBTC is backed by: - 0.5 BTC underlying. - \$26 K worth of USDC vault collateral. The vault CR is $$ \frac{\text{\$26\ K}}{\text{\$20\ K}} = 1.3 $$, equal to the vault's minimal CR. - \$60 K worth of FLR in pool collateral, of which \$10 K belongs to the agent. The pool CR is $$ \frac{\text{\$60\ K}}{\text{\$20\ K}} = 3 $$, above the pool's minimal CR. 2. Now the price of BTC increases from \$20K to $30K. As a result: - The vault CR is $$ \frac{\text{\$26\ K}}{\text{\$30\ K}} \approx 0.87 $$, **way below the vault's 1.3 minimal CR**. - The pool CR is $$ \frac{\text{\$60\ K}}{\text{\$30\ K}} = 2 $$, **below the vault's 2.5 minimal CR**. To comply with the vault's 1.3 minimal CR, the agent needs $$ 1.3 \times \text{\$30\ K} = \text{\$39\ K} $$ of USDC vault collateral, which he does not have. **Warning:** At this point, all the agent's FAssets backed by this vault must be liquidated. 3. A liquidator notices this situation and decides to liquidate 1 FBTC, currently worth $30K. The liquidation premium factor is 1.1, so the liquidator receives $33K worth of assets: - \$26 K worth of USDC, which is all of the collateral in the agent's vault. - \$7 K worth of FLR. Note that the portion of payment in FLR is higher than in Example 1 because enough USDC in collateral did not exist. At this point, the agent is backing 0 FBTC, and the remaining collateral is: - 0.5 BTC underlying. - \$0 worth of USDC in vault collateral. - \$53 K worth of FLR in pool collateral, of which \$3 K belongs to the agent. All this collateral can be freely withdrawn by its owners. Because this collateral is not backing any FAssets anymore, no part of it is locked.
Example with a very large price movement. A price increment such that the vault plus the pool collateral is not enough to back the minted FAssets results in a combined CR lower than 1. By design, liquidation payments will never exceed the combined CR times the liquidated amount, so, in this case, liquidation is not a profitable operation. Moreover, the collateral locked in the FAssets system might not be a strong enough deterrent for agents that want to dispose of the higher-valued underlying in an illegal way.
## Liquidation Triggers Some events related to liquidation are not detected automatically and must be triggered by entities external to the blockchain. These entities are [liquidators](/fassets/overview#liquidators) and [challengers](/fassets/overview#challengers). Anyone can be a liquidator or a challenger and earn rewards for contributing to the correct working of the FAssets system. Some triggers put an agent in liquidation mode, and some others get agents out of liquidation mode. ### Liquidation-Enabling Triggers - A valid liquidation request is submitted, triggering the liquidation automatically. - A liquidator triggers a liquidation manually, but does not submit a liquidation request immediately, seeking a better premium, because the premium might increase as time passes. - A liquidator detects that the CR is below the [CCB](/fassets/collateral#system-wide-thresholds) and sets the start time for an agent. This operation does not immediately trigger the liquidation. Instead, it starts a timer that enables the liquidation to be triggered after a system-defined time has elapsed. - A [proof of illegal activity](#illegal-payments) is presented, which immediately triggers a full liquidation. ### Liquidation-Disabling Triggers After an agent enters the liquidation state, it remains there until its CR exceeds the [safety CR](/fassets/collateral#system-wide-thresholds) again. The following operations can increase an agent's CR and can, therefore, potentially get the agent out of the liquidation state: - Redemptions. - A liquidation improves the agent's position. - The agent deposits more collateral. - The agent self-closes a position. - After the price has moved so that the agent's position is healthy again, the agent, or someone on the agent's behalf, manually sets the liquidation state to false. Exiting the liquidation state as soon as possible is in the agent's best interest, even if the agent might re-enter it again soon. Premiums paid to liquidators might depend on how long the agent has been in liquidation, for example. Also, exiting the liquidation state resets the CCB timer. ## Tracking the Underlying Balance Agents are required to keep a certain percentage of underlying asset for each backed FAsset. This percentage, called the [backing factor](/fassets/overview#agents), is stored at an address on the underlying chain controlled by the agent. This requirement is enforced by balance-tracking in the FAsset contract. To track balances, the system must receive reports for each payment sent and received at the agent's address: - Incoming payments are part of the [minting process](/fassets/minting) and are updated as the process occurs. - Outgoing payments are either part of the [redemption process](/fassets/redemption) or illegal payments, which are penalized. Challengers maintain the health of the FAssets system by monitoring the agent's underlying address to identify illegal operations that can make the agent's underlying backing too low. Challengers that correctly report illegal operations receive rewards from the agent's vault collateral. The following subsections contain details about all the topics that must be considered when monitoring an agent's underlying balance. ### Chain Fees Fees for gas on the underlying chain can create issues for the FAssets system, so part of tracking an agent's underlying balance involves tracking the amount spent on fees on the underlying chain. Expensive gas fees can cause an address to have fewer assets than it should have and trigger a liquidation. Therefore, consider these actions: - **Cap the gas usage on underlying chains**: On smart-contract chains, the Flare Data Connector defines a cap on the gas amount to enable any simple transaction to pass. If senders limit their gas amount to this cap and a transaction still fails due to insufficient gas, the failure is considered the receiver's fault, and the transaction is labeled as blocked. The gas cap is defined by the Flare Data Connector, not the FAssets system, because it is the Flare Data Connector that labels transactions as blocked. - **Maintain the underlying balance**: Agents must ensure that the payment plus the transaction fee for a redemption never reduce their balance to an amount lower than the amount required to back the FAssets. Agents can ensure that redemptions do not reduce that balance in several ways: - They can honor redemptions from some other address. On UTXO chains, they can also honor redemptions from a combination of addresses. - They can top up the underlying address and then send proof of payment to update the tracked balance. After a redemption begins, the agent has a limited time to comply, so topping-up is time-sensitive. ### Underlying Withdrawals Agents might legally withdraw part of the funds on their underlying address in several ways: - **Minting fees**: A part of a minter's payment is the [mint fee](/fassets/minting#fees) in the underlying asset. - **Failed redemptions**: When an address is backing assets and those assets were redeemed, but the agent [does not pay the redeemer](/fassets/redemption#redemption-payment-failure), the redeemer is paid with collateral, and the agent can withdraw the assets. - **Liquidated assets**: If an agent's position was partially or fully liquidated, the agent can withdraw the assets. - **Self-closed assets**: After an agent [self-closes](/fassets/minting#self-minting), the closed assets can be withdrawn. The FAssets system must keep track of the agent's underlying funds, so when performing the above legal withdrawals, agents must still adhere to the following process: 1. Announce the withdrawal to the FAssets system and obtain a payment reference. 2. Perform the withdrawal, using the payment reference. 3. Use the [FDC](/fdc/overview) to obtain a proof of payment. 4. Present the proof of payment to the FAssets system, which clears the announcement. If the agent does not present the proof of payment, anyone can present it after a while and receive a reward from the agent's vault. Enabling nonagents to present this proof helps the FAssets system keep track of underlying balances. Only one withdrawal announcement can be active per agent at any time to prevent the agent from overwhelming the balance-tracking system with many simultaneous small withdrawals. ### Illegal Payments Any challenger can report illegal payments from an underlying address and receive rewards in return. An illegal payment always triggers a full liquidation, which cannot be stopped. An agent can still escape paying the liquidation premium by self-closing before liquidators submit their liquidation requests, but the agent's vault remains unusable and must be closed. To resume operations, the agent must open a new vault with a different underlying address. The challenge system ensures that all minted FAssets are always backed by the assets on the agent's underlying address in the required percentage. Malicious agents might try to remove those assets in different ways. Therefore, challengers can report illegal activities by using these different _challenges_: #### Illegal Payment Challenge A payment from the agent's underlying address without a payment reference or with a payment reference that does not correspond to any open [redemption](/fassets/redemption) or [announced withdrawal](#underlying-withdrawals). This challenge is performed in the following way: 1. The challenger obtains proof of the illegal payment using the FDC. 2. The challenger presents the proof to the FAssets system, which triggers: - A vault collateral payment from the agent's vault to the challenger's address as a reward. - The agent's state for the address is set to [full liquidation](#liquidation-process). #### Double Payment Challenge An agent might try to abuse a redemption request to pay to the redeemer and use the same payment reference to pay an amount to the agent's own address. An agent might even try to pay the redeemer multiple times when he is redeeming against himself. This activity is easy to detect after the first payment is reported in [step 6 of the redemption process](/fassets/redemption#redemption-process), because then the request is deleted and the second payment becomes illegal. However, a malicious agent might try to issue the second payment before reporting the completion of the first one. The double payment challenge catches this attempt as soon as the payments are finalized, regardless of whether they have been reported to the FAssets system. This challenge is performed in the following way: 1. The challenger detects two seemingly legal payments from the same agent's underlying address and with equal payment reference, and obtains proofs for both using the Flare Data Connector. 2. The challenger presents the two proofs to the FAssets system and triggers the reward payment and full liquidation. #### Negative Balance Challenge One or more legal payments can make the balance on the agent's underlying address too small or equivalently make the free underlying balance negative. This situation can happen because gas fees might be unknown when redemptions are approved. This situation would normally be detected after all payments are reported, but with this approach it can be caught as soon as the payments are finalized on the underlying chain: 1. The challenger detects one or more legal payments from the same agent's underlying address and the total outgoing amount exceeds the sum of all redemption values plus the total free balance. The challenger obtains proofs for all of them using the Flare Data Connector. 2. The challenger presents all the proofs to the FAssets system, which checks that the transactions are from the agent's underlying address, that they have not been confirmed yet, and that their total really makes the free balance negative. Then, it triggers reward payment and full liquidation. ### Time Lock for Withdrawing Collateral The agent's collateral backs minted FAssets but also pays challenge fees and possibly illegal payment penalties. Because finalization on some underlying chains takes a long time, challenges can sometimes be proved to be valid only after an agent's position is already closed and enough collateral to pay them is not available. For this reason, collateral withdrawals are locked for a certain amount of time before they become effective. The amount of time varies depending on the underlying chain and the time frame required for achieving finality on that chain. For agents, any collateral withdrawals must be announced, and then the amount is locked for some time before it can be withdrawn. The locked collateral is also ineligible for minting. Agents must announce the closing of their vaults. They become unusable until the lock expires, and then they can be closed. :::tip[What's next] Learn more about the different components and processes involved in FAssets - [collateral](/fassets/collateral), [minting](/fassets/minting), [redemptions](/fassets/redemption) and [Core Vault](/fassets/core-vault). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## Emergency Pause System ## Overview The FAssets system includes a structured **three-level emergency pause mechanism** that allows governance to selectively restrict operations in response to planned upgrades, critical failures, or security threats. Each level defines a different scope of restrictions, ensuring a balance between **security, system availability, and user protection**. ## Emergency Pause Levels ### Start Operations This level is intended for scenarios like network upgrades or planned maintenance. The goal is to prevent anyone from initiating new actions while allowing operations already in progress to complete before proceeding with maintenance. #### Halted Operations - **User/Agent Operations**: [minting](/fassets/minting), [redeeming](/fassets/redemption), [liquidation](/fassets/liquidation), destroy agent, collateral withdrawal, exit available. - **One-Step Operations**: self mint, self close, enter, create agent. - **Collateral Pool Operations**: enter, exit, withdraw fees, [pay fee debt](/fassets/collateral#minting-fees-and-debt), delegate pool collateral. #### Use Cases - Scheduled network upgrades. - System configuration or parameter changes. - Non-critical security patches. ### Full This level is designed for critical emergencies. It halts practically all activity while preserving essential system functions. #### Halted Operations Stops almost everything, except: - **FAsset transfers**, but users can still move their assets. - **Settings modifications**, but governance can still adjust parameters. - **System upgrades**, but critical updates can still be deployed. #### Use Cases - Confirmed security vulnerabilities. - Market manipulation attempts. - Critical system failures. - Regulatory compliance requirements. ### Full and Transfer The most restrictive level that freezes all transactional activity, including asset transfers. #### Halted Operations This is identical to the FULL level, but it also stops the **FAsset transfers**. It completely freezes all movement of the FAssets. #### Use Cases - Severe security breaches. - Complete system compromise. - Emergency asset protection. - Extreme market conditions. ## Conclusion The FAssets emergency pause system provides a robust framework for maintaining system security and stability during critical situations. The system can respond appropriately to various types of threats while maintaining essential functionality and protecting user assets. :::tip[What's next] Learn more about the different components and processes involved in FAssets - [collateral](/fassets/collateral), [minting](/fassets/minting), [redemptions](/fassets/redemption), [liquidations](/fassets/liquidation) and [Core Vault](/fassets/core-vault). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## Operational Parameters This page lists the current values for the most important parameters of the FAssets system on **Songbird Canary-Network** and **Songbird Testnet Coston**. These values are subject to change as the system is further developed and tested. ## Asset Manager Operational Parameters To get the default agent settings, you need to call the `getSettings` function on the `IAssetManager` interface. Read more about the `IAssetManager` interface [here](/fassets/reference/IAssetManager). ### Minting and Redeeming ### Payment Times ### Collateral Ratios ### Liquidation ### Rewarding ### Time Locks ### Emergency Pause ## Core Vault ### Core Vault Manager To get the Core Vault manager operational parameters you need to use the [`ICoreVaultManager`](/fassets/reference/ICoreVaultManager) interface. Specific functions added to each parameter. ### Core Vault Settings To get the Core Vault settings you need to use the [`IAssetManager`](/fassets/reference/IAssetManager) interface. Specific functions added to each parameter. ## Direct Minting ## Minting Tag Manager ## Redeem With Tag :::tip[What's next] Learn more about the different components and processes involved in FAssets - [collateral](/fassets/collateral), [minting](/fassets/minting), [redemptions](/fassets/redemption), [liquidations](/fassets/liquidation) and [Core Vault](/fassets/core-vault). For developer resources, explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FAssets integration. ::: --- ## FAssets Reference ## Deployed Contracts ## References ### Core Interfaces Smart contract interfaces for interacting with the FAssets system. --- ## FAssets on Songbird The launch of FAssets on Songbird Canary-Network demonstrates system behavior while paving the way for its next deployment on Flare Mainnet. The primary goals of this test are to ensure the system operates as intended, identify edge cases, refine usability and automation, and incentivize whitehat security researchers to uncover potential code errors. The test on Songbird Canary-Network will have the following characteristics: | Parameters | Description | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | FAsset Sequence | XRP will be tested first, followed by either BTC or DOGE. | | Agent Whitelisting | FAssets agents must be whitelisted by Flare Foundation to perform their roles. | | Caps and Losses | Flare Foundation will underwrite up to \$300,000 in FAsset issuance to cover any losses resulting from system issues, while imposing a cap of $2 million in issuance per asset. | | Duration of the Test | Each FAsset will be tested on Songbird for at least 6 weeks until no issues have been found. | | FAssets Minting dApps | FAssets system users can access the frontend web interface for minting and redeeming: - [`https://fasset.oracle-daemon.com/sgb`](https://fasset.oracle-daemon.com/sgb) - [`https://sgb-fassets.au.cc/`](https://sgb-fassets.au.cc/) | | System Integrity and FAsset Pricing | During the Songbird test, restrictions and incentives may cause the FAsset price to deviate from the underlying currency's value. The current focus is on testing system integrity, not price alignment. | | Vault Collateral | USDX will serve as collateral for FAsset agent vaults. To ensure sufficient support for FAsset issuance and possible liquidations on Songbird, a large amount of USDX has been minted. | :::tip[Help improve FAssets] To participate, begin by joining the Flare Network FAssets Songbird [Telegram channel](https://t.me/FlareSupport) or contact [support@flare.network](mailto:support@flare.network). ::: --- ## Get FXRP Asset Manager Address :::note Network coverage Examples in this guide target the **Coston2** testnet for safety. For Songbird or Flare mainnet, swap the `coston2/` segment of every `@flarenetwork/flare-periphery-contracts/coston2/...` import for `songbird/` or `flare/` respectively, and resolve the AssetManager via the corresponding network's `IFlareContractRegistry`. RPC endpoints, faucet links, and the FAssets agents available on each network are listed on the [FAssets overview](/fassets/overview). ::: ## Overview When building on the Flare Network, it is important to **avoid hardcoding contract addresses**, especially for key components such as the FXRP Asset Manager. These addresses can change between Flare testnets and mainnet deployments, and relying on fixed values can lead to broken integrations. Instead, you should dynamically fetch the FXRP Asset Manager address using the [Flare Contract Registry](/network/guides/flare-contracts-registry), the trusted source for obtaining contract addresses on any Flare network. ## Get the FXRP Asset Manager Address To get the [FAssets asset manager](/fassets/reference/IAssetManager) address, you can use the [Flare Contract Registry](/network/guides/flare-contracts-registry) library. It is included in the [Flare Periphery Contracts package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts). The following example demonstrates how to get the FXRP Asset Manager address using the Flare Contract Registry library in a Solidity contract. ```solidity // Get the FXRP Asset Manager address from the Flare contracts registry IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP(); ``` ## Summary In this guide, you learned how to get the FXRP Asset Manager address for the FAssets system by interacting with the `AssetManager` contract using the [`IAssetManager`](/fassets/reference/IAssetManager) interface. You should not hardcode the FXRP Asset Manager address in your smart contracts. :::tip[What's next] To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint). - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Redeem FXRP with Tag ## Overview This guide walks you through redeeming FXRP using [`redeemWithTag`](/fassets/reference/IAssetManager#redeemwithtag) on the `AssetManagerFXRP` contract. Compared to [`redeemAmount`](/fassets/developer-guides/fassets-redeem-amount), `redeemWithTag` adds a required XRPL destination tag that the agent must include in the redemption payment — useful when redeeming directly to an exchange address that requires a tag. The complete runnable example is available in the [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/fassets/redeem-with-tag.ts) repository. ## Prerequisites - An EVM wallet with. - Native tokens on the same wallet to cover the gas fees. - An XRPL destination address and the destination tag the recipient requires. ## Redeem with Tag Script {FAssetsRedeemWithTag} ## Code Breakdown 1. Set `REDEEM_AMOUNT_UBA` to the amount of FXRP to redeem in UBA (underlying base unit). 2. Set `REDEEMER_UNDERLYING_ADDRESS_STRING` to the XRPL address that will receive the redeemed XRP from the agent. 3. `EXECUTOR_ZERO_ADDRESS` is used because no executor is appointed in this example. Pass a real address to delegate default-handling to an executor. 4. Set `REDEMPTION_DESTINATION_TAG` to the XRPL destination tag the agent must include on the redemption payment. It must fit in a 32-bit integer. See [minting with tag](/fassets/direct-minting#minting-tag-manager) for more details or check out the [minting with tag example](/fassets/developer-guides/fassets-direct-minting-tag) on how to reserve a tag. 5. Resolve the `AssetManagerFXRP` address through the [Flare Contract Registry](/network/guides/flare-contracts-registry) using [`getContractAddressByName`](/network/guides/flare-contracts-registry). 6. Read [`minimumRedeemAmountUBA`](/fassets/reference/IAssetManager#minimumredeemamountuba) and check that the requested amount is at least the protocol minimum. 7. Simulate the [`redeemWithTag`](/fassets/reference/IAssetManager#redeemwithtag) call to validate the arguments and produce a signed request payload. 8. Submit the redemption request transaction. 9. Wait for the transaction receipt. 10. Decode [`RedemptionWithTagRequested`](/fassets/reference/IAssetManagerEvents#redemptionwithtagrequested) events from the receipt logs with the `parseEventLogs` function and select the one whose `redeemer` matches the caller. A single call may emit multiple `RedemptionWithTagRequested` events when multiple agents fulfill the request. ## Important Notes - **XRP-only feature.** `redeemWithTag` is an XRP-only feature and is only available for FXRP. - **`minimumRedeemAmountUBA` is enforced on-chain.** Requests below the threshold revert. - **The redemption may be partial.** If the request requires too many redemption tickets, only part is filled and the leftover is returned via [`RedemptionAmountIncomplete`](/fassets/reference/IAssetManagerEvents#redemptionamountincomplete). Call `redeemWithTag` again for the remainder. - **Multiple agents may pay.** A single `redeemWithTag` call can produce several [`RedemptionWithTagRequested`](/fassets/reference/IAssetManagerEvents#redemptionwithtagrequested) events — one per agent serving the request. Track each `requestId` for the agent's underlying-chain payment. - **Default uses a dedicated proof type.** Confirming a tagged redemption uses `confirmXRPRedemptionPayment`, and if the agent fails to pay call `xrpRedemptionPaymentDefault` to start the default process. :::tip[What's next] To continue your FAssets development journey, you can: - Redeem any amount without a tag with [`redeemAmount`](/fassets/developer-guides/fassets-redeem-amount). - Handle non-paying agents in [Monitor Redemptions & Execute Defaults](/fassets/developer-guides/fassets-redemption-default). - Mint without a tag using [memo-based direct minting](/fassets/developer-guides/fassets-direct-minting) or [direct minting with tag](/fassets/developer-guides/fassets-direct-minting-tag). ::: --- ## Redeem FAssets ## Overview In this guide, you will learn how to redeem FAssets using the Asset Manager smart contract. Redemption is the process of burning FAssets (e.g., FXRP) on Flare in exchange for their equivalent value on the original chain (e.g., XRP on XRPL). See the [Redemption](/fassets/redemption) overview for more details. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) ## Redeem FAssets Smart Contract The following example demonstrates how to redeem FAssets using the `AssetManager` smart contract. {FAssetsRedeem} {/* prettier-ignore */} Open in Remix ### Code Breakdown 1. Redeem the FAssets using the [`redeem`](/fassets/reference/IAssetManager#redeem) function by specifying the number of lots to redeem and the underlying chain address. 2. Retrieve the asset manager settings to calculate the redeemed amount; for this, you need to obtain the `lotSizeAMG` and `assetDecimals` from the asset manager settings [document](/fassets/developer-guides/fassets-settings-solidity). 3. Get the redemption request info to check the redemption status. :::info In this example, you are not using the executor vault address, but you can use it to redeem FAssets on behalf of another address. ::: ## Deploy and Interact with the Smart Contract To deploy the contract and redeem FAssets, you can use the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). Create a new file, for example, `scripts/fassets/redeem.ts` and add the following code: ### Import the Required Contracts At first, you need to import the required dependencies and smart contract TypeScript types: ```typescript ``` ### Define the Constants Define the constants: - `LOTS_TO_REDEEM` - the number of lots to redeem; - `UNDERLYING_ADDRESS` - underlying chain address where the redeemed asset will be sent; ```typescript const LOTS_TO_REDEEM = 1; const UNDERLYING_ADDRESS = "rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm"; ``` ### Import Contract Artifacts Import the contract artifacts to interact with the smart contracts: - `FAssetsRedeem` - the `FAssetsRedeem` contract; - `IAssetManager` - the asset manager interface; - `IERC20` - the `IERC20` contract for FXRP token; ```typescript const FAssetsRedeem = artifacts.require("FAssetsRedeem"); const AssetManager = artifacts.require("IAssetManager"); const IERC20 = artifacts.require("IERC20"); ``` ### Deploy and Verify the Redeem Contract Deploy and verify the smart contract providing the asset manager address as a constructor argument: ```typescript async function deployAndVerifyContract() { const fAssetsRedeem: FAssetsRedeemInstance = await FAssetsRedeem.new(); const fAssetsRedeemAddress = fAssetsRedeem.address; try { await run("verify:verify", { address: fAssetsRedeemAddress, constructorArguments: [], }); } catch (e: any) { console.log(e); } console.log("FAssetsRedeem deployed to:", fAssetsRedeemAddress); return fAssetsRedeem; } ``` ### Approve the FXRP transfer to the Redeem Contract To redeem FAssets, you must approve a sufficient amount of transfer of FXRP to the `FAssetsRedeem` contract address after it is deployed, as it acts as the invoker during the redemption process. ```typescript async function approveFAssets(fAssetsRedeem: any, amountToRedeem: string) { console.log("Approving FAssetsRedeem contract to spend FXRP..."); const fxrpAddress = await fAssetsRedeem.getFXRPAddress(); const fxrp: ERC20Instance = await IERC20.at(fxrpAddress); const approveTx = await fxrp.approve( await fAssetsRedeem.address, amountToRedeem, ); console.log("FXRP approval completed"); } ``` :::warning In a production environment, it is recommended to use a more secure method for approving the transfer of FXRP to a smart contract. ::: ### Parse the Redemption Events During the redemption process, the `AssetManager` emits events: - [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) - holds the agent vault address, redemption request id, the amount of FAssets to redeem, and other important information. - [`RedemptionTicketCreated`](/fassets/reference/IAssetManagerEvents#redemptionticketcreated) - holds the redemption ticket information updated during the redemption process. - [`RedemptionTicketUpdated`](/fassets/reference/IAssetManagerEvents#redemptionticketupdated) - holds the redemption ticket information updated during the redemption process. To parse the redemption events, you can use the following function: ```typescript async function parseRedemptionEvents( transactionReceipt: any, fAssetsRedeem: any, ) { console.log("\nParsing events...", transactionReceipt.rawLogs); const redemptionRequestedEvents = logEvents( transactionReceipt.rawLogs, "RedemptionRequested", AssetManager.abi, ); logEvents( transactionReceipt.rawLogs, "RedemptionTicketCreated", AssetManager.abi, ); logEvents( transactionReceipt.rawLogs, "RedemptionTicketUpdated", AssetManager.abi, ); return redemptionRequestedEvents; } ``` :::info The `logEvents` function is used to parse and log the events interacting using the [Truffle](https://archive.trufflesuite.com/) suite of tools. You can find this function in the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). In this guide, we are not focusing on the event parsing. ::: ### Output the Redemption Result The following function is used to output the redemption request information. ```typescript async function printRedemptionRequestInfo( fAssetsRedeem: any, redemptionRequestedEvents: any[], ) { console.log("\n=== Redemption Request Information ==="); for (const event of redemptionRequestedEvents) { const redemptionRequestInfo = await fAssetsRedeem.getRedemptionRequestInfo( event.decoded.requestId, ); console.log("Redemption request info:", redemptionRequestInfo); } } ``` ### Redeeming FAssets To put it altogether you can use the following function to deploy the contract, transfer FXRP to it, redeem the FAssets, and parse the redemption events: ```typescript async function main() { // Deploy and verify the contract const fAssetsRedeem = await deployAndVerifyContract(); // Get the lot size and decimals to calculate the amount to redeem const settings = await fAssetsRedeem.getSettings(); const lotSize = settings[0]; const decimals = settings[1]; console.log("Lot size:", lotSize.toString()); console.log("Asset decimals:", decimals.toString()); // Calculate the amount to redeem according to the lot size and the number of lots to redeem const amountToRedeem = web3.utils .toBN(lotSize) .mul(web3.utils.toBN(LOTS_TO_REDEEM)); console.log( `Required FXRP amount ${formatUnits(amountToRedeem.toString(), Number(decimals))} FXRP`, ); console.log(`Required amount in base units: ${amountToRedeem.toString()}`); // Approve FXRP for redemption await approveFAssets(fAssetsRedeem, amountToRedeem.toString()); // Call redeem function and wait for transaction const redeemTx = await fAssetsRedeem.redeem( LOTS_TO_REDEEM, UNDERLYING_ADDRESS, ); console.log("Redeem transaction receipt", redeemTx); // Parse events from the transaction const redemptionRequestedEvents = await parseRedemptionEvents( redeemTx.receipt, fAssetsRedeem, ); // Print redemption request info for each redemption requested event await printRedemptionRequestInfo(fAssetsRedeem, redemptionRequestedEvents); } main().catch((error) => { console.error(error); process.exitCode = 1; }); ``` ### Run the Script To run the script, use the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) with the following command: ```bash yarn hardhat run scripts/fassets/redeem.ts --network coston2 ``` Once the script is executed, two events hold the important information: #### [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) The `RedemptionRequested` event contains the agent vault address, redeemer address, underlying chain address, amount of FAssets to redeem, redemption fee, and other important information. ```bash RedemptionRequested Result { '0': '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D', '1': '0xDc8B65D8C07825280F2A2E08D5b25B9eea892E8f', '2': '6797056', '3': 'rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm', '4': '10000000', '5': '50000', '6': '9113416', '7': '9113939', '8': '1753190628', '9': '0x464250526641000200000000000000000000000000000000000000000067b700', '10': '0x0000000000000000000000000000000000000000', '11': '0', __length__: 12, agentVault: '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D', redeemer: '0xDc8B65D8C07825280F2A2E08D5b25B9eea892E8f', requestId: '6797056', paymentAddress: 'rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm', valueUBA: '10000000', feeUBA: '50000', firstUnderlyingBlock: '9113416', lastUnderlyingBlock: '9113939', lastUnderlyingTimestamp: '1753190628', paymentReference: '0x464250526641000200000000000000000000000000000000000000000067b700', executor: '0x0000000000000000000000000000000000000000', executorFeeNatWei: '0' } ``` When decoding an event, the most important data from the event is: - The agent vault address that will redeem the FAssets is `0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D`; - The redeemer address is `0xCA7C9fBbA56E44C508bcb4872775c5fEd169cDb3`; - The redemption ticket id is `1628`; - The underlying chain address is `rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm`; - The amount of FAssets to redeem is `20000000`; - The redemption fee is `20000`; You can view the full event description [here](/fassets/reference/IAssetManagerEvents#redemptionrequested). #### [`RedemptionTicketUpdated`](/fassets/reference/IAssetManagerEvents#redemptionticketupdated) The event `RedemptionTicketUpdated` contains the redemption ticket information, including the agent vault address, redemption ticket ID, and the value of the redemption ticket in the underlying chain currency. For every minting, a redemption ticket is created, and during the redemption process, the redemption ticket is updated with the new redemption status. ```bash RedemptionTicketUpdated Result { '0': '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D', '1': '1628', '2': '5360000000', __length__: 3, agentVault: '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D', redemptionTicketId: '1628', ticketValueUBA: '5360000000' } ``` Once decoding the most important data from the event is: - The agent vault address that will redeem the FAssets is `0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D`; - The redemption ticket id is `1628`; - The value of the redemption ticket in underlying chain currency is `5360000000` (partially redeemed). You can read the full event description [here](/fassets/reference/IAssetManagerEvents#redemptionticketupdated). ### Agent Process The FAssets agent should perform the redemption, and the user must retrieve the redeemed assets from the agent. These [operational parameters](/fassets/operational-parameters) define the timeframe within which the agent must complete the redemption payment: - `underlyingBlocksForPayment`: The number of underlying blocks during which the agent can pay the underlying value. - `underlyingSecondsForPayment`: The minimum time allowed for an agent to pay for a redemption. If the agent is unable to pay the redemption payment in the specified time, the redeemer can: 1. Make a proof of [payment non-existence](/fdc/attestation-types/referenced-payment-nonexistence) using the [Flare Data Connector](/fdc/overview). 2. Execute the [`redemptionPaymentDefault`](/fassets/reference/IAssetManager#redemptionpaymentdefault) function and present the proof of payment non-existence to the FAssets system, which triggers a redemption failure. 3. The redeemer receives collateral plus a premium. ## Video Tutorial ## Summary This is only the first step of the redemption process. The redeemer or agent completes the redemption process when proof of payment is presented to the FAssets system. Read more about the redemption process in the [here](/fassets/redemption). :::tip[What's next] To continue your FAssets development journey, you can: - Learn how to [mint FAssets using the executor](/fassets/developer-guides/fassets-mint-executor). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Swap and Redeem FAssets ## Overview In this guide, you will learn how to swap a token (in this example, WCFLR) for FXRP and redeem FXRP for FAssets using the Uniswap V2 router (in this example, BlazeSwap) and the FAssets asset manager. Redemption is the process of burning FAssets (e.g., FXRP) on Flare in exchange for their equivalent value on the original chain (e.g., XRP on XRPL). See the [Redemption](/fassets/redemption) overview for more details. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) ## Swap and Redeem Smart Contract The following Solidity contract demonstrates how to swap WCFLR for FXRP and then redeem FXRP to receive XRP on the XRPL. ### Contract Code {FAssetsSwapAndRedeemContract} This contract has two main functions: - Swaps WCFLR for FXRP using a BlazeSwap router (Uniswap V2 compatible). - Redeems FAssets (FXRP) to an XRPL address using the FAssets asset manager. ### 1. Required Imports The contract uses the following dependencies: - `IERC20` OpenZeppelin standard ERC-20 interface. - `IAssetManager` interface to the FAssets Asset Manager. - `AssetManagerSettings` is used to retrieve lot size and configuration. ### 2. Interfaces The `ISwapRouter` interface allows interaction with a Uniswap V2-compatible router: - `swapExactTokensForTokens`: Swaps an exact amount of input tokens. - `getAmountsIn`: Calculates how much input is needed for a given output. ### 3. State Variables and Constructor Defined state variables: - `router`: Uniswap V2 router contract; in this example, it is the BlazeSwap router. - `assetManager`: Asset Manager handling FXRP redemption. - `token`: Token to swap (WCFLR in the swap path). - `swapPath`: Array of addresses defining the swap route (e.g., WCFLR → FXRP). To instantiate the contract, the constructor sets up the Uniswap router, FAssets asset manager, and swap path. It initializes the token as the first token in the swap path. ### 4. Main Function: `swapAndRedeem` The `swapAndRedeem` is the core function that executes the swap and redemption flow. - **1. Validation** - Ensure the caller has sufficient WCFLR balance. - Confirm the contract has enough FXRP allowance for redemption. - **2. Transfer** - Move WCFLR from the caller to the contract. - Approve the router to spend WCFLR on behalf of the contract. - **3. Swap** - Perform the swap from WCFLR to FXRP using the specified swap path. - Apply a 10-minute deadline for the swap operation. - **4. Redemption** - Redeem the obtained FXRP through the FAssets system. - Transfer the resulting XRP to the caller's XRPL address. - **5. Helper Function: `calculateRedemptionAmountIn`** - Accept the number of FXRP lots intended for redemption. - Fetch the lot size from the FAssets asset manager and calculate the WCFLR needed for swap and redemption. ### 5. Helper Function: `calculateRedemptionAmountIn` This function calculates the amount of WCFLR needed to swap to FXRP and redeem. ### 6. Helper Function: `_redeem` - Redeem the obtained FXRP through the FAssets system. - Transfer the resulting XRP to the caller's XRPL address. ## Execute the Swap and Redeem To execute the swap and redeem process, you need to deploy the smart contract instance and call the `swapAndRedeem` function. {FAssetsSwapAndRedeemScript} ### Code Breakdown - **1. Dependencies and Constants** - `LOTS_TO_REDEEM`: The number of FAsset lots to redeem (typically set to 1). - `UNDERLYING_ADDRESS`: The XRPL address that will receive the redeemed assets. - `SWAP_ROUTER_ADDRESS`: The address of the Uniswap V2-compatible swap router. - `WC2FLR`: Wrapped CFLR token address. - **2. Deploy and Verify** - Deploys the `SwapAndRedeem` contract and verifies it using [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). - **3. Calculate Redemption Amounts** - Calls `calculateRedemptionAmountIn` to determine the required WCFLR amount. - **4. Approve Tokens** - Uses the ERC-20 `approve` method to allow the contract to spend WCFLR. The approved allowance must cover `amountIn` (the WCFLR being spent), not `amountOut` (the FXRP received). The current starter script over-approves on `amountOut`; a follow-up starter PR will switch it to `amountIn`. :::warning In a production environment, you should use a secure method to approve spending the tokens. ::: - **5. Execute the Swap and Redemption** - Calls `swapAndRedeem` to complete the FAssets redemption process. ### Run the Script To run the script, use the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) with the following command: ```bash yarn hardhat run scripts/fassets/swapAndRedeem.ts --network coston2 ``` The script outputs transaction details, including swap amounts and redemption results. :::tip For an in-depth explanation of the FAssets redemption process, refer to the [FAssets Redeem Guide](/fassets/developer-guides/fassets-redeem). ::: ## Summary In this guide, you learned how to: - swap WCFLR for FXRP using Uniswap V2 compatible router (in this example, BlazeSwap); - redeem FXRP to XRP on the XRP Ledger using FAssets asset manager. The complete code is in the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). :::tip[What's next] To continue your FAssets development journey, you can: - Learn how to [mint FAssets using the executor](/fassets/developer-guides/fassets-mint-executor). - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Read FAssets Agent Details ## Overview In this guide, you will learn how to read FAssets agent details such as agent name, description, logo, and terms of use utilizing the [AgentOwnerRegistry](/fassets/reference/IAgentOwnerRegistry) smart contract. This information is essential for building user interfaces that display agent information or for validating agent credentials in your applications. ## Prerequisites - Basic understanding of [FAssets system](/fassets/overview). - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package. ## Understanding FAssets Agents FAssets agents are entities that manage the minting and redemption of FAssets tokens. Each agent has: - **Management Address**: The address that controls the agent's operations. - **Agent Name**: Agent's display name. - **Description**: Detailed information about the agent. - **Icon URL**: Link to the agent's logo or branding image. - **Terms of Use URL**: Link to the agent's terms and conditions. ## Step-by-Step Implementation ### Get the AgentOwnerRegistry Contract Address The [AgentOwnerRegistry](/fassets/reference/IAgentOwnerRegistry) contract address is stored in the FAssets Asset Manager settings. Here's how to retrieve it: ```solidity // Get the Asset Manager instance IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP(); // Retrieve the AgentOwnerRegistry address from settings address agentOwnerRegistry = assetManager.getSettings().agentOwnerRegistry; ``` ### Access the AgentOwnerRegistry Contract Create an interface instance to interact with the AgentOwnerRegistry: ```solidity IAgentOwnerRegistry agentOwnerRegistryContract = IAgentOwnerRegistry(agentOwnerRegistry); ``` ### Read Agent Details The [AgentOwnerRegistry](/fassets/reference/IAgentOwnerRegistry) provides several functions to retrieve agent information. All functions require the agent's management address as a parameter: #### Available Functions - [`getAgentName(address _managementAddress)`](/fassets/reference/IAgentOwnerRegistry#getagentname) - Returns the agent's display name. - [`getAgentDescription(address _managementAddress)`](/fassets/reference/IAgentOwnerRegistry#getagentdescription) - Returns the agent's description. - [`getAgentIconUrl(address _managementAddress)`](/fassets/reference/IAgentOwnerRegistry#getagenticonurl) - Returns the URL to the agent's icon/logo. - [`getAgentTermsOfUseUrl(address _managementAddress)`](/fassets/reference/IAgentOwnerRegistry#getagenttermsofuseurl) - Returns the URL to the agent's terms of use. #### Complete Example Function Here's a complete Solidity language function that retrieves all agent details in a single call: ```solidity function getAgentDetails(address _managementAddress) external view returns (string memory name, string memory description, string memory iconUrl, string memory termsOfUseUrl) { // Get Asset Manager and AgentOwnerRegistry IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP(); address agentOwnerRegistryAddress = assetManager.getSettings().agentOwnerRegistry; IAgentOwnerRegistry agentOwnerRegistry = IAgentOwnerRegistry(agentOwnerRegistryAddress); // Retrieve all agent details name = agentOwnerRegistry.getAgentName(_managementAddress); description = agentOwnerRegistry.getAgentDescription(_managementAddress); iconUrl = agentOwnerRegistry.getAgentIconUrl(_managementAddress); termsOfUseUrl = agentOwnerRegistry.getAgentTermsOfUseUrl(_managementAddress); return (name, description, iconUrl, termsOfUseUrl); } ``` ## Summary In this guide, you learned how to read FAssets agent details using the [AgentOwnerRegistry](/fassets/reference/IAgentOwnerRegistry) smart contract. This functionality is crucial for building user interfaces that display agent information and for validating agent credentials in your applications. The complete implementation examples are available in the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). :::tip Next Steps To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint) - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) - Explore [FAssets system settings](/fassets/operational-parameters) ::: --- ## Monitor Redemptions & Execute Defaults ## Overview In the FAssets system, once a **redeemer submits a redemption request**, the assigned **agent must send the underlying asset** (e.g., XRP) to the redeemer within a specific time defined in the [operational parameters](/fassets/operational-parameters). This guide explains: - How to **observe the redemption deadline**. - What parameters to use (`underlyingSecondsForPayment`, `underlyingBlocksForPayment`). - How to **handle agent failure** by calling [`redemptionPaymentDefault`](/fassets/reference/IAssetManager#redemptionpaymentdefault). ## Prerequisites - Basic understanding of the [FAssets system](/fassets/overview). - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package. ## Key Operational Parameters The FAssets system has specific operational parameters that control the timing of redemption payments. These parameters define both the minimum time window and block range during which an agent must complete the underlying payment. | Parameter | Unit | Purpose | | ----------------------------- | ------- | ------------------------------------------------------------------------------------ | | `underlyingSecondsForPayment` | seconds | The minimum time allowed for an agent to pay for a redemption. | | `underlyingBlocksForPayment` | blocks | The number of underlying blocks during which the agent can pay the underlying value. | Both values are defined per asset in the FAssets system and can be fetched from the [Asset Manager contract](/fassets/developer-guides/fassets-settings-solidity). ## Monitoring the Redemption Window When a redemption is created, the [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) event is emitted by the Asset Manager contract. From this event, you can obtain the following: - `requestId`: unique identifier for the redemption. - `agentVault`: the agent vault responsible for the payment. - `redeemer`: address requesting redemption. - `underlyingAddress`: the destination address on the underlying chain. - `paymentReference`: a unique hash for proof tracking. - `firstUnderlyingBlock`: the first underlying block to submit payment. - `lastUnderlyingBlock`: the last underlying block to submit payment. - `lastUnderlyingTimestamp`: the **deadline** on the underlying chain for submitting the redemption payment. Wait until the deadline passes, then check whether the agent made the redemption payment on the underlying chain. These two values work together as a dual safety mechanism: `lastUnderlyingBlock` ensures the payment happens within a reasonable number of blocks, while `lastUnderlyingTimestamp` guarantees the payment occurs within a reasonable amount of time. Both constraints must be satisfied for the payment to be considered valid, providing robust protection against timing-based attacks. The [`RedemptionPerformed`](/fassets/reference/IAssetManagerEvents#redemptionperformed) event is emitted by the Asset Manager contract when the redemption payment is completed. :::info The simplest way to observe if a redemption payment has been made is to use the [xrpl](https://www.npmjs.com/package/xrpl) Node.js package to monitor incoming transactions to the `underlyingAddress` (the redeemer's address on the underlying chain). Wait until the `underlyingSecondsForPayment` and `underlyingBlocksForPayment` deadlines pass, then check if any incoming transactions match the expected redemption amount. To verify the payment is for the correct redemption, compare the memo data in the XRP transaction to the `paymentReference` from the `RedemptionRequested` event. ::: ## Handling Non-Payment If the agent fails to complete the redemption in the XRP Ledger, the redeemer or the executor can initiate the redemption default process. ### Generate the Proof of Payment Non-Existence With the information from the [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) event, you can generate the attestation request for the [ReferencedPaymentNonexistence](/fdc/attestation-types/referenced-payment-nonexistence) attestation type. Use the following parameters: - `minimalBlockNumber`: The starting block number of the search range, which is the value of `firstUnderlyingBlock`. - `deadlineBlockNumber`: The block number to include as the end of the search range, which is the value of `lastUnderlyingBlock`. - `deadlineTimestamp`: The timestamp to include as the end of the search range, which is the value of `lastUnderlyingTimestamp`. - `destinationAddressHash`: The standard hash of the address where the payment was expected, which is the value of `underlyingAddress`. - `amount`: The required payment amount in minimal units, which is the value of `valueUBA`. - `standardPaymentReference`: The specific payment reference that should have been included, which is the value of `paymentReference`. - `checkSourceAddresses`: Not used in case of XRP Ledger, always set to false. - `sourceAddressesRoot`: Not used in case of XRP Ledger, always set to zero address. Create the attestation request with the Flare Data Connector and wait for the attestation request to be confirmed. :::info Check the [ReferencedPaymentNonexistence](/fdc/attestation-types/referenced-payment-nonexistence) attestation type for more details, and the [FDC by hand](/fdc/guides/fdc-by-hand) guide for more information on how to create the attestation request. ::: ### Execute the Redemption Default After the attestation request is confirmed, you can retrieve the proof of payment for the non-existence of the redemption payment on the XRP Ledger. The redeemer or the executor can execute the redemption default process by calling the [`redemptionPaymentDefault`](/fassets/reference/IAssetManager#redemptionpaymentdefault) function on the AssetManager contract. This function requires two parameters: - `_proof`: The proof of payment non-existence from the FDC of the redemption payment on the XRP Ledger. - `_redemptionRequestId`: The request ID of the redemption request from the `RedemptionRequested` event. It does the following: - Checks if the proof of payment non-existence is valid. - Verifies that both the `underlyingSecondsForPayment` and `underlyingBlocksForPayment` deadlines have passed. - Executes the compensation payment to the redeemer or the executor from the agent's collateral. - Releases the agent's locked collateral. - Emits the [`RedemptionDefaulted`](/fassets/reference/IAssetManagerEvents#redemptiondefault) event. ## Video Tutorial ## Summary In this guide, you learned how to monitor redemption requests and handle agent payment failures in the FAssets system: - **Monitor the deadlines**: Track the [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) event and wait for the payment deadline to pass. - **Generate proof**: Create a [`ReferencedPaymentNonexistence`](/fdc/attestation-types/referenced-payment-nonexistence) attestation using the [Flare Data Connector](/fdc/overview). - **Execute default**: Call [`redemptionPaymentDefault`](/fassets/reference/IAssetManager#redemptionpaymentdefault) to trigger compensation from the agent's collateral. This process ensures redeemers can recover their funds when agents fail to meet their payment obligations. :::tip Next Steps To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint) - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) - Explore [FAssets system settings](/fassets/operational-parameters) ::: --- ## Get Redemption Queue ## Overview In this guide, you will learn about the redemption queue and how it functions. You will also learn how to get redemption tickets using the [AssetManager](/fassets/reference/IAssetManager) smart contract. Finally, you will learn how to calculate the total value and the number of lots to redeem. ## Prerequisites - Basic understanding of the [FAssets system](/fassets/overview). - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package. ## Redemption Queue When a user mints FAssets, a [redemption ticket](/fassets/reference/IAssetManagerEvents#redemptionticketcreated) is created and added to the **redemption queue**. This queue determines the order in which redemptions are fulfilled and assigns the corresponding agents to return the underlying asset (e.g., XRP) to the redeemers. The redemption queue is a **FIFO (First-In-First-Out)** queue that is shared globally across all agents. It consists of ticket entries, where each ticket represents lots minted with a particular agent. ## `maxRedeemedTickets` System Setting The `maxRedeemedTickets` system setting defines the maximum number of tickets that can be redeemed in a single request. This setting is used to prevent high gas consumption and ensure the fairness of the redemption process. To get the `maxRedeemedTickets` setting, you can use the [getSettings](/fassets/reference/IAssetManager#getsettings) function from the [AssetManager](/fassets/reference/IAssetManager) smart contract. ## Fetch Redemption Queue Data ### Global Redemption Queue The [`redemptionQueue`](/fassets/reference/IAssetManager#redemptionqueue) function returns the redemption queue in the form of an array of [`RedemptionTicketInfo`](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/data/RedemptionTicketInfo.sol) structs. Each `RedemptionTicketInfo` includes: - `agentVault`: address of the backing agent. - `ticketValueUBA`: value of the ticket in underlying base amount (UBA). - `lots`: number of lots. - `createdAt`: creation timestamp. ### Agent's Redemption Queue The [`agentRedemptionQueue`](/fassets/reference/IAssetManager#agentredemptionqueue) function returns the redemption queue for a specific agent in the form of an array of [`RedemptionTicketInfo`](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/data/RedemptionTicketInfo.sol) structs. ## How It Ties Together The `redemptionQueue` function provides the **global** ticket order, where each ticket is linked to a **specific agent**. The agent associated with each ticket must fulfill their part of the redemption. Once a ticket is consumed, it is removed from the queue, and the queue is updated accordingly. ## Example The following example demonstrates how to get `maxRedeemedTickets`, the global redemption queue, and calculate the number of lots to redeem. {FAssetsGetRedemptionQueue} ### Code Explanation The script: 1. Import the function that gets the AssetManager XRP contract. 2. Get the `AssetManager` contract instance by calling the `getAssetManagerFXRP` function. 3. Get the settings from the AssetManager contract. 4. Get the `maxRedeemedTickets` value from the system settings (maximum number of tickets that can be redeemed in a single request). The actual number of tickets returned may be less than `maxRedeemedTickets` if the queue is shorter. 5. Get the lot size value from the settings (minimum quantity required for minting FAssets). 6. Get the redemption queue by calling the contract method `redemptionQueue(_firstRedemptionTicketId, _pageSize)` — passing `0` as the first id starts at the **beginning of the queue** (it does not refer to ticket index `0`). The method returns `(_queue, _nextRedemptionTicketId)`; use `_nextRedemptionTicketId` to paginate. 7. Sum all ticket values in the redemption queue to calculate the total value in UBA (Underlying Asset Base Amount) 8. Calculate the total lots in the redemption queue by dividing the total value by the lot size. :::info The `getFXRPAssetManager` function is a utility function that is included in the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). It is used to get the FXRP Asset Manager address from the Flare Contract Registry. ::: ### Run the Script To run the script, use the following command: ```bash yarn hardhat run scripts/fassets/getRedemptionQueue.ts --network coston2 ``` The script will output the redemption queue data for the FAssets system on the [Coston2](/network/overview) network: ```bash Total value in redemption queue (UBA): 4190000000 Total lots in redemption queue: 419 ``` ## Summary The guide covers: - **Redemption Queue Mechanics**: How the queue works as a shared global system tracking redemption tickets. - `maxRedeemedTickets` **Setting**: Maximum number of tickets that can be redeemed in a single request. - **Queue Access Methods**: How to retrieve global and agent-specific queues using contract functions. - **Practical Implementation**: A TypeScript example showing how to get settings, retrieve the queue, and calculate total value and lots to redeem. :::tip Next Steps To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint) - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) - Explore [FAssets system settings](/fassets/operational-parameters) ::: --- ## Check Direct Minting Limits ## Overview This guide reads the on-chain [direct minting](/fassets/direct-minting#rate-limits) rate limits, replays the tumbling-window state off-chain, and prints the maximum amount a single mint can request right now without being delayed. Direct minting is throttled by the `MintingRateLimiter` library: an hourly and a daily window cap how much FXRP can be minted before the asset manager starts pushing new mintings into a future window via [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed). A pre-flight check lets a frontend or executor avoid surprising the user with a delay. The complete runnable example is available in the [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/fassets/direct-minting-limits.ts) repository. ## How the windows work `MintingRateLimiter` library uses **clock-aligned tumbling windows**, not rolling windows. On initialization, the window start is snapped to a multiple of `windowSizeSeconds`: ```text windowStartTimestamp = block.timestamp - block.timestamp % windowSizeSeconds ``` With `windowSizeSeconds = 3600`, the hourly window aligns to UTC hour boundaries (00:00-01:00, 01:00-02:00, …). The daily window (`86400` seconds) aligns to 00:00 UTC. On every write, the limiter advances the window: ```text windowsElapsed = (now - windowStartTimestamp) / windowSizeSeconds mintedInCurrentWindow = subOrZero(mintedInCurrentWindow, windowsElapsed * maxPerWindow) windowStartTimestamp += windowsElapsed * windowSizeSeconds ``` Two consequences worth noting: 1. **Unused capacity does not roll over.** `subOrZero` clamps at zero, so an idle hour does not give twice the cap the next hour — every fresh window starts at exactly `maxPerWindow`. 2. **Over-cap mints are delayed, not rejected.** `executionAllowedAt` is set to `windowStartTimestamp + windowSize * mintedInCurrentWindow / maxPerWindow`, so overflow drains hour-by-hour through the `subOrZero` step. Reading the limiter state on its own returns stale `(windowStart, minted)` values until the next write touches the contract, so this script replays the slide off-chain to show the values as they would be right now. A mint can be delayed by either the hourly/daily window or the large-minting threshold — whichever pushes [`executionAllowedAt`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) further into the future wins. ## Prerequisites - The [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter) cloned locally with dependencies installed. - A configured `publicClient` pointing at the [Flare network](/network/overview) you want to query (Coston2 by default in the starter). ## Direct Minting Limits Script {FAssetsDirectMintingLimits} ## Code Breakdown 1. **Window sizes.** `HOURLY_WINDOW_SECONDS` and `DAILY_WINDOW_SECONDS` are the `windowSizeSeconds` values the `MintingRateLimiter.sol` library uses — `3600` aligns to UTC hour boundaries, `86400` aligns to 00:00 UTC. 2. **Format helpers.** `formatUba` prints raw UBA (units of basis assets) alongside the XRP equivalent via `dropsToXrp`, and `formatTimestamp` shows ISO time with a relative offset so it is clear whether a timestamp is in the future or the past. 3. **Replay the limiter slide.** `computeWindowState` mirrors the window-advancement logic in `MintingRateLimiter.sol`: it advances `windowStart` past every tumble that has elapsed and drains `mintedInCurrentWindow` by `windowsElapsed * limit`. Without this off-chain replay, you would see stale numbers between writes. 4. **Print one window's live state.** `printWindow` shows the live cap, used amount, and percentage, remaining headroom, the effective window start, and the next UTC tumble. 5. **Resolve `AssetManagerFXRP`.** `getContractAddressByName("AssetManagerFXRP")` looks up the asset manager through the [Flare Contract Registry](/network/guides/flare-contracts-registry). 6. **Read everything in parallel.** `Promise.all` fetches the [AMG granularity](/fassets/reference/IAssetManager#assetmintinggranularityuba), the [hourly](/fassets/reference/IAssetManager#getdirectmintinghourlylimituba) and [daily](/fassets/reference/IAssetManager#getdirectmintingdailylimituba) caps, the raw limiter state for [hourly](/fassets/reference/IAssetManager#getdirectmintinghourlylimiterstate) and [daily](/fassets/reference/IAssetManager#getdirectmintingdailylimiterstate) windows, the [`unblockUntilTimestamp`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp), and the [large-minting threshold](/fassets/reference/IAssetManager#getdirectmintinglargemintingthresholduba) and [delay](/fassets/reference/IAssetManager#getdirectmintinglargemintingdelayseconds) in one round trip. 7. **Convert AMG to UBA.** The limiter stores `mintedInCurrentWindow` in AMG (`uint64`) for cheap on-chain storage. Multiplying by [`assetMintingGranularityUBA`](/fassets/reference/IAssetManager#assetmintinggranularityuba) rebases it into UBA (units of basis assets) so it can be compared against the UBA-denominated cap. 8. **Honor the unblock flag.** When [`getDirectMintingsUnblockUntilTimestamp`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp) returns a future timestamp, governance has temporarily turned off the limiter; treat full caps as available. 9. **Pre-flight gate.** `bigintMin(hourlyHeadroom, dailyHeadroom)` is the largest amount that fits both windows. A request larger than this will not revert — it will mint with [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) and an `executionAllowedAt` in the future. ## Important Notes - **Large mintings have an independent fixed delay.** Mintings above [`directMintingLargeMintingThresholdUBA`](/fassets/reference/IAssetManager#getdirectmintinglargemintingthresholduba) are delayed by [`directMintingLargeMintingDelaySeconds`](/fassets/reference/IAssetManager#getdirectmintinglargemintingdelayseconds) even if the hourly and daily windows have headroom. - **Reads are stale between writes.** Always advance the window off-chain when displaying live limiter state. - **Plan UI flows around [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed)** so users are not surprised by a deferred execution. :::tip[What's next] To continue your FAssets development journey, you can: - Mint with the 32-byte memo flow in [Direct Mint FXRP](/fassets/developer-guides/fassets-direct-minting). - Mint with a reserved tag in [Direct Mint FXRP with Tag](/fassets/developer-guides/fassets-direct-minting-tag). - Read the protocol details in [Direct Minting](/fassets/direct-minting). ::: --- ## Read FAssets Settings (Solidity) ## Overview This guide will show you how to fetch FAsset [lot size](/fassets/minting#lots) for FXRP using the Solidity smart contract on the Flare network. You will deploy the contract using Hardhat and interact with it via a TypeScript script. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Node.js, TypeScript, and hardhat.config.ts with the Songbird Testnet Coston network configured ## Create Smart Contract to Fetch FAsset Lot Size To get the FAsset lot size, you can use the following smart contract: {FAssetsAssetManagerSettingsLotSize} {/* prettier-ignore */} Open in Remix ### Code Breakdown 1. Import the [ContractRegistry library](/network/guides/flare-contracts-registry#contract-registry-library) to access the Flare Network contract registry. 2. Import the interface `IAssetManager` from the [Flare Periphery Contracts package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts), which provides access to the FAssets system. 3. Create a contract called `FAssetsSettings` that will be used to fetch the FAssets settings from the asset manager. 4. Use the ContractRegistry to get the FAssets FXRP asset manager address. 5. Use the `getLotSize` function to retrieve settings from the FAssets FXRP asset manager. The function calls [`getSettings`](/fassets/reference/IAssetManager#getsettings) which returns the complete asset manager settings that you can find in the [FAssets Operational Parameters](/fassets/operational-parameters/#asset-manager-operational-parameters) documentation: - `lotSizeAMG`: The smallest amount you can trade (in AMG units). - `assetDecimals`: How many decimal places the FAssets asset uses. 6. The `getLotSize` function returns two values: - `lotSizeAMG`: The smallest amount you can trade (in AMG units). - `assetDecimals`: How many decimal places the FAssets asset uses. :::info Flare Hardhat Starter Kit Using the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) you can quickly deploy the contract and generate TypeChain bindings for seamless TypeScript integration. It streamlines development by providing a ready-made project structure, preconfigured networks, and needed packages. ::: ## Deploy and Interact with the Smart Contract Use the following TypeScript script to deploy the contract and fetch the FAsset settings using the Hardhat tool: ```typescript title="scripts/fassets/getLotSize.ts" // yarn hardhat run scripts/fassets/getLotSize.ts --network coston2 // 1. Get the contract artifact const FAssetsSettings = artifacts.require("FAssetsSettings"); async function main() { // 2. Deploy the contract const fAssetsSettings = await FAssetsSettings.new(); console.log("FAssetsSettings deployed to:", fAssetsSettings.address); // 3. Call getSettings function const lotSize = await fAssetsSettings.getLotSize(); console.log("Lot size:", lotSize[0]); console.log("Decimals:", lotSize[1]); // 4. Convert lot size to XRP const lotSizeFXRP = Number(lotSize[0]) / Math.pow(10, Number(lotSize[1])); console.log("Lot size in XRP", lotSizeFXRP); } main().catch((error) => { console.error(error); process.exitCode = 1; }); ``` ### Code Explanation 1. Get the contract artifact `FAssetsSettings` from the Hardhat artifacts. 2. Deploy the `FAssetsSettings` on the network and get the contract address. 3. Call the `getLotSize` function to get the lot size and asset decimals. 4. Convert the lot size to XRP. ## Run the Script Now you can run the script using the Hardhat tool with the following command: ```bash yarn hardhat run scripts/fassets/getLotSize.ts --network coston2 ``` You should see the following output: ```bash Compiled 1 Solidity file successfully (evm target: cancun). Deploying FAssetsSettings... FAssetsSettings deployed to: 0x40deEaA76224Ca9439D4e1c86F827Be829b89D9E Lot size: 20000000n Decimals: 6n Lot size in XRP 20 ``` :::info This script is included in the [Flare Hardhat Starter Kit](https://github.com/flare-foundation/flare-hardhat-starter). ::: ## Exploring Additional Parameters The FAssets asset manager exposes key parameters like collateral ratios, minting fees, and liquidation thresholds via the `getSettings` function. See the [FAssets Operational Parameters](/fassets/operational-parameters/#asset-manager-operational-parameters) for details. ## Summary Congratulations! You have now deployed a Solidity helper contract and used a TypeScript script to: - Fetch FAsset FXRP lot size and decimals - Convert the value to a user-friendly format using decimal precision. :::tip[What's next] To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint). - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Read FAssets Settings (Node) ## Overview In this guide, you will build a TypeScript script that connects to the [Flare Testnet Coston2](/network/solidity-reference) and: - Fetches [FAssets configuration settings](/fassets/operational-parameters) and gets the [lot size](/fassets/minting#lots) for FXRP - Retrieves the XRP/USD price from the [FTSO](/ftso/overview) - Calculates the USD value of one FAssets FXRP lot This guide is a perfect first step for developers working with FAssets. ## Prerequisites - [Node.js](https://nodejs.org/en/download/) - [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm/) - [TypeScript](https://www.typescriptlang.org/download/) - [Viem](https://viem.sh) - [Flare Wagmi Periphery Package](https://www.npmjs.com/package/@flarenetwork/flare-wagmi-periphery-package) ## Project Setup ### Create Project Directory Create a new directory for your project and initialize a new npm project: ```bash mkdir fassets-settings-ftso cd fassets-settings-ftso npm init -y ``` ### Install Dependencies Install the following dependencies: ```bash npm install --save-dev \ typescript \ viem \ @flarenetwork/flare-wagmi-periphery-package ``` ### Configure TypeScript Create a `tsconfig.json` file: ```bash npx tsc --init ``` Update `tsconfig.json` as follows: ```json title="tsconfig.json" { "compilerOptions": { "rootDir": "./scripts", "outDir": "./dist", "module": "esnext", "moduleResolution": "node", "target": "esnext", "lib": ["dom", "dom.iterable", "esnext"], "types": ["node"], "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true }, "include": ["scripts/**/*.ts"], "exclude": ["node_modules"] } ``` ### Update Package Configuration Change `package.json` to use ES modules and add a build script: ```json "type": "module", "scripts": { "build": "tsc" } ``` ## Implementation ### Create Script File ```bash mkdir scripts touch scripts/fassets-settings.ts ``` Open `scripts/fassets-settings.ts` in your favorite code editor. ### Import Dependencies Import viem to interact with the blockchain and the `coston2` namespace from the [`@flarenetwork/flare-wagmi-periphery-package`](https://www.npmjs.com/package/@flarenetwork/flare-wagmi-periphery-package), which provides all typed contract ABIs for the Coston2 network: ```typescript ``` ### Define Constants ```typescript const FLARE_CONTRACT_REGISTRY_ADDRESS = "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019" as const; const XRP_USD_FEED_ID = "0x015852502f55534400000000000000000000000000" as const; ``` ### Create a Client Create a viem public client connected to Coston2: ```typescript const client = createPublicClient({ chain: flareTestnet, transport: http(), }); ``` ### Get the FAssets FXRP Asset Manager Address Resolve the FXRP Asset Manager address from the [Flare Contract Registry](/network/guides/flare-contracts-registry): ```typescript const assetManagerAddress = await client.readContract({ address: FLARE_CONTRACT_REGISTRY_ADDRESS, abi: coston2.iFlareContractRegistryAbi, functionName: "getContractAddressByName", args: ["AssetManagerFXRP"], }); ``` ### Implement Settings Retrieval Fetch the FAssets configuration settings using the [`getSettings`](/fassets/reference/IAssetManager#getsettings) function and calculate the FXRP lot size: ```typescript const settings = await client.readContract({ address: assetManagerAddress, abi: coston2.iAssetManagerAbi, functionName: "getSettings", }); const lotSizeFXRP = Number(settings.lotSizeAMG) / Math.pow(10, Number(settings.assetDecimals)); console.log("Lot Size (FXRP):", lotSizeFXRP); ``` :::info The [`getSettings`](/fassets/reference/IAssetManager#getsettings) function returns the complete asset manager settings that you can find in the [FAssets Operational Parameters](/fassets/operational-parameters/#asset-manager-operational-parameters) documentation. ::: ## Convert Lot Size to USD To convert the lot size to USD, fetch the XRP/USD [anchor price feed](/ftso/scaling/anchor-feeds/) from the [FTSO](/ftso/overview). ### Get the FtsoV2 Address ```typescript const ftsoAddress = await client.readContract({ address: FLARE_CONTRACT_REGISTRY_ADDRESS, abi: coston2.iFlareContractRegistryAbi, functionName: "getContractAddressByName", args: ["FtsoV2"], }); ``` ### Get the Price Feed `getFeedById` is a `payable` function, so use `simulateContract` to call it without sending a transaction: ```typescript const { result: [_value, _decimals, _timestamp], } = await client.simulateContract({ address: ftsoAddress, abi: coston2.ftsoV2InterfaceAbi, functionName: "getFeedById", args: [XRP_USD_FEED_ID], value: 0n, }); ``` ### Convert Lot Size to USD ```typescript const xrpUsdPrice = Number(_value) / Math.pow(10, Number(_decimals)); const lotValueUSD = lotSizeFXRP * xrpUsdPrice; console.log("XRP/USD Price:", xrpUsdPrice); console.log("Lot value in USD:", lotValueUSD); console.log("Timestamp:", _timestamp.toString()); ``` ## Putting All Together ```typescript title="scripts/fassets-settings.ts" const FLARE_CONTRACT_REGISTRY_ADDRESS = "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019" as const; const XRP_USD_FEED_ID = "0x015852502f55534400000000000000000000000000" as const; const client = createPublicClient({ chain: flareTestnet, transport: http(), }); const assetManagerAddress = await client.readContract({ address: FLARE_CONTRACT_REGISTRY_ADDRESS, abi: coston2.iFlareContractRegistryAbi, functionName: "getContractAddressByName", args: ["AssetManagerFXRP"], }); const settings = await client.readContract({ address: assetManagerAddress, abi: coston2.iAssetManagerAbi, functionName: "getSettings", }); const lotSizeFXRP = Number(settings.lotSizeAMG) / Math.pow(10, Number(settings.assetDecimals)); console.log("Lot Size (FXRP):", lotSizeFXRP); const ftsoAddress = await client.readContract({ address: FLARE_CONTRACT_REGISTRY_ADDRESS, abi: coston2.iFlareContractRegistryAbi, functionName: "getContractAddressByName", args: ["FtsoV2"], }); const { result: [_value, _decimals, _timestamp], } = await client.simulateContract({ address: ftsoAddress, abi: coston2.ftsoV2InterfaceAbi, functionName: "getFeedById", args: [XRP_USD_FEED_ID], value: 0n, }); const xrpUsdPrice = Number(_value) / Math.pow(10, Number(_decimals)); const lotValueUSD = lotSizeFXRP * xrpUsdPrice; console.log("XRP/USD Price:", xrpUsdPrice); console.log("Lot value in USD:", lotValueUSD); console.log("Timestamp:", _timestamp.toString()); ``` ## Running the Script ```bash npm run build node dist/fassets-settings.js ``` You should see the following output: ```bash Lot Size (FXRP): 10 XRP/USD Price: 2.843861 Lot value in USD: 28.43861 Timestamp: 1756977702 ``` ## Exploring Additional Parameters The FAssets asset manager exposes key parameters like collateral ratios, minting fees, and liquidation thresholds via the `getSettings` function. See the [FAssets Operational Parameters](/fassets/operational-parameters/#asset-manager-operational-parameters) for details. ## Summary Congratulations! You have built a TypeScript script that connects to the Coston2 network and retrieves the FAsset configuration settings and the price of XRP in USD. :::tip[What's next] To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint). - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Direct Mint FXRP ## Overview This guide walks you through minting FAssets FXRP using [direct minting](/fassets/direct-minting): a single XRPL payment to the FXRP [Core Vault](/fassets/core-vault) with a memo that encodes the Flare-side recipient. An executor then finalizes the mint on Flare, and the recipient receives FXRP. The complete runnable example is available in the [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/fassets/direct-mint.ts) repository. ## How it differs from standard minting Compared to the standard [collateral reservation flow](/fassets/developer-guides/fassets-mint), direct minting is a single-step XRPL payment: - No collateral reservation — the minter does not call [`reserveCollateral`](/fassets/developer-guides/fassets-mint#reserve-collateral) and there is no agent selection. - A single XRPL payment to the Core Vault address replaces the agent payment. - The recipient (and optionally the executor) is encoded in the XRPL memo or destination tag. - An executor calls `executeDirectMinting` on Flare to finalize and receives a flat fee. This guide demonstrates the **32-byte memo format**, where the recipient is a Flare address and any executor can finalize. ## Prerequisites - [Flare smart account](/smart-accounts/overview) controlled by your XRPL wallet. - Testnet XRP on the XRP Ledger Testnet usage. Get testnet XRP from the [XRP Testnet Faucet](https://xrpl.org/resources/dev-tools/xrp-faucets). ## Direct Minting Memo The memo is a packed 32-byte [`PaymentReference`](/fassets/direct-minting#memo-field): | Bytes | Value | Meaning | | :---- | :----------------- | :------------------------------------------------------------ | | 0-7 | `4642505266410018` | Direct minting prefix. | | 8-11 | `00000000` | Zero padding to fill the 32-byte memo. | | 12-31 | 20-byte recipient | EVM address of the Flare smart account receiving FXRP tokens. | Because no executor is encoded, anyone can finalize this minting on Flare. For executor-restricted minting, use the 48-byte memo format described in [Memo Field](/fassets/direct-minting#memo-field). ## Direct Minting Script {FAssetsDirectMinting} ## Code Breakdown 1. Define the direct minting prefix constant `DIRECT_MINTING_PREFIX`. 2. `buildDirectMintingMemo` concatenates the prefix, four zero bytes, and the recipient address (without the `0x` prefix, lowercased) into a 32-byte hex string ready to be passed as [`MemoData`](https://xrpl.org/docs/references/protocol/transactions/common-fields#memos-field) on the XRPL payment. 3. `sendDirectMintPayment` builds the memo for the recipient and submits an XRPL `Payment` transaction to the [Core Vault](/fassets/core-vault) address with `memos: [{ Memo: { MemoData } }]`, returning the resulting transaction. 4. Set the net FXRP amount to mint in XRP. Minting and executor fees are added to the net amount to form the XRPL payment amount. 5. Connect to the XRPL Testnet using `XRPL_TESTNET_RPC_URL` and load the sender wallet from `XRPL_SEED`. 6. Resolve the recipient — the Flare [`PersonalAccount`](/smart-accounts/reference/IPersonalAccount) for the XRPL wallet — via [`getPersonalAccountAddress`](/smart-accounts/reference/IPersonalAccount), and look up `AssetManagerFXRP` through the [Flare Contract Registry](/network/guides/flare-contracts-registry) using [`getContractAddressByName`](/network/guides/flare-contracts-registry). 7. Read three values in parallel: - `getDirectMintingPaymentAddress` — the Core Vault XRPL address that receives the payment. - `getFxrpBalance` — the recipient's FXRP balance before minting. - `computeDirectMintingPaymentAmountXrp` — the gross XRP amount equal to the net mint amount plus the minting fee and the executor fee, as quoted by the `AssetManagerFXRP` contract. 8. Send the XRPL payment to the Core Vault with the recipient memo via the `sendDirectMintPayment` function. 9. Wait for the [`DirectMintingExecuted`](/fassets/reference/IAssetManagerEvents#directmintingexecuted) event on `AssetManagerFXRP` using `waitForDirectMintingExecuted` function. The event's `mintedAmountUBA`, `mintingFeeUBA`, and `executorFeeUBA` describe how the underlying payment was split. 10. Read the final FXRP balance and log the delta. ## Important Notes - **Fees are deducted from the underlying payment.** The XRP sent to the [Core Vault](/fassets/core-vault) must cover the net mint amount plus the minting fee and the executor fee. - **If the payment is below the minimum minting fee**, no FXRP is minted, and the entire payment is forfeited to the fee receiver. - **Rate limits may delay execution.** When hourly, daily, or large-minting thresholds are hit, the minting emits [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) instead of [`DirectMintingExecuted`](/fassets/reference/IAssetManagerEvents#directmintingexecuted), with an `executionAllowedAt` timestamp. - **For repeat minters**, prefer the destination-tag flow via `IMintingTagManager` — it avoids constructing a memo per payment and lets you set a preferred executor. :::tip[What's next] To continue your FAssets development journey, you can: - Mint with tag with [`mintWithTag`](/fassets/developer-guides/fassets-direct-minting-tag). - Read the protocol details in [Direct Minting](/fassets/direct-minting). - Redeem FAssets with [`redeemAmount`](/fassets/developer-guides/fassets-redeem-amount) or [`redeemWithTag`](/fassets/developer-guides/fassets-redeem-with-tag). ::: --- ## Direct Mint FXRP with Tag ## Overview This guide walks you through minting FXRP using [direct minting](/fassets/direct-minting) with an **XRPL destination tag** reserved on Flare that maps to a recipient (and optionally an executor). Once a tag is reserved and bound, every XRPL payment to the [Core Vault](/fassets/core-vault) carrying that destination tag mints FXRP to the configured recipient — no per-payment memo required. The complete runnable example is available in the [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/fassets/direct-mint-tag.ts) repository. ## Tag vs. memo Compared to the [memo-based direct minting](/fassets/developer-guides/fassets-direct-minting), the tag flow is better suited for repeat minters: - **One-time setup**: reserve a tag once via [`MintingTagManager`](/fassets/reference/IMintingTagManager) — pay the [`reservationFee`](/fassets/reference/IMintingTagManager#reservationfee) in native token and bind the recipient with [`setMintingRecipient`](/fassets/reference/IMintingTagManager#setmintingrecipient). - **Reusable**: every subsequent XRPL payment with the same destination tag routes to the bound recipient without rebuilding a memo. - **Transferable**: tags are [ERC-721 NFTs](https://ethereum.org/developers/docs/standards/tokens/erc-721/) and can be transferred to another owner with [`transfer`](/fassets/reference/IMintingTagManager#transfer). - **Optional preferred executor**: the tag owner may restrict execution via [`setAllowedExecutor`](/fassets/reference/IMintingTagManager#setallowedexecutor). After [`othersCanExecuteAfterSeconds`](/fassets/reference/IAssetManager#getdirectmintingotherscanexecuteafterseconds), anyone can finalize. ## Prerequisites - [Flare smart account](/smart-accounts/overview) controlled by your XRPL wallet. - Native FLR (or C2FLR on Coston2) on the Flare wallet to cover the tag reservation fee. - Testnet XRP on the XRP Ledger Testnet — get it from the [XRP Testnet Faucet](https://xrpl.org/resources/dev-tools/xrp-faucets). - Environment variables: - `XRPL_TESTNET_RPC_URL` — XRPL Testnet RPC endpoint. - `XRPL_SEED` — seed for the XRPL wallet that will send the payment. - `MINTING_TAG` — _optional_. If set, the script reuses this tag instead of reserving a new one. ## Direct Minting Tag Script {FAssetsDirectMintingTag} ## Code Breakdown 1. `reserveTag` function reads the [`reservationFee`](/fassets/reference/IMintingTagManager#reservationfee) from [`MintingTagManager`](/fassets/reference/IMintingTagManager) contract and calls [`reserve`](/fassets/reference/IMintingTagManager#reserve) with the fee. The contract assigns the next available tag and returns it. 2. `setMintingRecipient` function binds the tag to a recipient address via [`setMintingRecipient`](/fassets/reference/IMintingTagManager#setmintingrecipient). Payments arriving at the [Core Vault](/fassets/core-vault) with this destination tag will mint FXRP to that recipient. 3. `getMintingRecipient` reads the [`mintingRecipient`](/fassets/reference/IMintingTagManager#mintingrecipient) currently configured for a tag. 4. `getOrReserveTag` reuses `MINTING_TAG` from the `.env` variables if set; otherwise, it reserves a new tag and binds it to the recipient. 5. Set the net FXRP amount to mint in XRP. Minting and executor fees are added to the the XRPL payment amount. 6. Connect to the XRPL Testnet using `XRPL_TESTNET_RPC_URL` and load the sender wallet from `XRPL_SEED`. 7. Resolve the recipient — the Flare [`PersonalAccount`](/smart-accounts/reference/IPersonalAccount) for the XRPL wallet — via [`getPersonalAccountAddress`](/smart-accounts/reference/IPersonalAccount), and look up `AssetManagerFXRP` through the [Flare Contract Registry](/network/guides/flare-contracts-registry) using [`getContractAddressByName`](/network/guides/flare-contracts-registry). 8. Resolve the [`MintingTagManager`](/fassets/reference/IMintingTagManager) contract address from the `AssetManager` using [`getMintingTagManager`](/fassets/reference/IAssetManager#getmintingtagmanager). 9. Reserve a new tag (or reuse one from `MINTING_TAG`) and confirm the configured recipient. 10. Read three values in parallel: - `getDirectMintingPaymentAddress` — the Core Vault XRPL address that receives the payment. - `getFxrpBalance` — the recipient's FXRP balance before minting. - `computeDirectMintingPaymentAmountXrp` — the gross XRP amount equal to the net mint amount plus the minting fee and the executor fee, as quoted by the `AssetManagerFXRP` contract. 11. Send the XRPL payment to the [Core Vault](/fassets/core-vault) with `destinationTag: Number(tag)` — no memo required. 12. Wait for the [`DirectMintingExecuted`](/fassets/reference/IAssetManagerEvents#directmintingexecuted) event on `AssetManagerFXRP` using the `waitForDirectMintingExecuted` function. The event's `mintedAmountUBA`, `mintingFeeUBA`, and `executorFeeUBA` describe how the underlying payment was split. 13. Read the final FXRP balance and log the delta. ## Important Notes - **Reservations cost native currency.** Tag reservation pays a fee in native token. Tags are assigned sequentially. - **Executor changes are not immediate.** After calling the [`setAllowedExecutor`](/fassets/reference/IMintingTagManager#setallowedexecutor) function, the new executor only becomes active after a 10-minute cooldown to protect in-flight FDC proofs. See [Executor Restrictions](/fassets/direct-minting#executor-restrictions). - **Transferring a tag resets it.** When ownership changes, the recipient is reset to the new owner, and the executor is reset to the zero address. - **Rate limits may delay execution.** If the minting hits hourly, daily, or large-minting thresholds, [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) is emitted instead of [`DirectMintingExecuted`](/fassets/reference/IAssetManagerEvents#directmintingexecuted), with an `executionAllowedAt` timestamp. ## Video Tutorial :::tip[What's next] To continue your FAssets development journey, you can: - Mint without a tag using [memo-based direct minting](/fassets/developer-guides/fassets-direct-minting). - Read the protocol details in [Direct Minting](/fassets/direct-minting). - Redeem FAssets with [`redeemAmount`](/fassets/developer-guides/fassets-redeem-amount) or [`redeemWithTag`](/fassets/developer-guides/fassets-redeem-with-tag). ::: --- ## Mint FAssets ## Overview This guide walks you through the complete process of minting FAssets (e.g., FXRP) on the Flare network. Minting FAssets is the process of wrapping underlying tokens (e.g., XRP from the XRP Ledger) into FAssets, enabling them to be used within the Flare blockchain ecosystem. See the [Minting](/fassets/minting) overview for more details. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) :::tip[Testing on Coston2] If you are testing on **Flare Testnet Coston2**, you can get testnet C2FLR and FXRP from the [Coston2 Faucet](https://faucet.flare.network/coston2) instead of going through the full minting process. For testnet XRP on the XRP Ledger, use the [XRP Testnet Faucet](https://xrpl.org/resources/dev-tools/xrp-faucets). ::: ## Minting Process Steps The minting process involves the following steps: 1. Reserve collateral from a suitable agent. 2. Send the underlying asset (e.g., XRP) to the agent. 3. Use [Flare Data Connector (FDC)](/fdc/overview) to generate proof of payment. 4. Execute minting with the proof to receive FAssets. ## Reserve Collateral Reserving collateral is the first step in the minting process. ### Minting Fees When reserving collateral, there are several fees that are paid. | Fee Type | Paid In | Details | | ------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Collateral Reservation Fee (CRF)** | Native tokens (FLR/SGB) | Compensates the agent and the collateral pool token (CPT) holders for locking their collateral during the minting process. Defined by governance as a percentage of the minted value. | | **Minting Fee** | Underlying currency (e.g., XRP for FXRP) | Main source of revenue for the agents and the CPT holders. Percentage of the minted amount (varies by agent). | | **Executor Fee (Optional)** | Native tokens (FLR/SGB) | Incentivizes the executor to process minting requests. Configurable fee denominated in FLR. | :::info[Collateral Reservation Fee (CRF)] If minting fails, the Collateral Reservation Fee is not returned to the minter. It is distributed to the agent and the pool in the same manner as the minting fee. ::: ### Reserve Collateral Script The following code demonstrates how to reserve collateral by calling the [`reserveCollateral`](/fassets/reference/IAssetManager#reservecollateral) function on the AssetManager contract. {FAssetsReserveCollateral} #### Collateral Reservation Script Breakdown 1. Define constants - `LOTS_TO_MINT`: Number of FAsset lots to reserve. - `ZERO_ADDRESS`: Placeholder for `executor` (not used in this script). 2. Retrieve and filter agents with enough free collateral and select the agent with the lowest fee and normal status. 3. Parse [`CollateralReserved`](/fassets/reference/IAssetManagerEvents#collateralreserved) event. 4. Start the minting reservation process at the script's entry point. 5. Call `findBestAgent` with the required number of lots. 6. Get the asset manager from the [Flare Contract Registry](/network/guides/flare-contracts-registry). 7. Find the best agent with enough free collateral lots. 8. Fetch agent metadata from [`getAgentInfo`](/fassets/reference/IAssetManager#getagentinfo) to get the agent's `feeBIPS`, which is used to calculate the collateral reservation fee. 9. Calculate the collateral reservation fee by calling [`collateralReservationFee`](/fassets/reference/IAssetManager#collateralreservationfee). 10. Reserve collateral from agent by calling [`reserveCollateral`](/fassets/reference/IAssetManager#reservecollateral) 11. Call `assetMintingDecimals` to determine the XRP token's decimal precision. 12. Parse the [`CollateralReserved`](/fassets/reference/IAssetManagerEvents#collateralreserved) event. 13. Calculate the total XRP value required for payment. ## Send Payment on XRP Ledger The next step is to send the XRP Ledger payment to the agent. Before making the payment, it is important to understand the payment timeframes and constraints. ### Payment Timeframes The minting process has specific timeframes for underlying payments, controlled by [operational parameters](/fassets/operational-parameters): | Parameter | Unit | Purpose | | ----------------------------- | ------- | ---------------------------------------------------------------------------- | | `underlyingSecondsForPayment` | seconds | The minimum time allowed for a minter to pay on the underlying chain | | `underlyingBlocksForPayment` | blocks | The number of underlying blocks during which the minter can make the payment | These parameters work together as a dual safety mechanism that ensures payment happens within a reasonable number of blocks and guarantees payment occurs within a reasonable amount of time. Both constraints must be satisfied for the payment to be considered valid. #### Payment Deadline Calculation When you reserve collateral, the system emits the following values in the [`CollateralReserved`](/fassets/reference/IAssetManagerEvents#collateralreserved) event: - `lastUnderlyingBlock`: The final block number for a valid payment. - `lastUnderlyingTimestamp`: The deadline timestamp for payment. Valid payments must occur **before both the last block AND the last timestamp**. #### Payment Failure Handling If the minter [fails to pay on the underlying chain](/fassets/minting#payment-failure) within the required timeframe: - The agent must prove [nonpayment](/fdc/attestation-types/referenced-payment-nonexistence) using the [Flare Data Connector](/fdc/overview). - After nonpayment is proved, the agent's collateral that was reserved is released. - The agent receives the [Collateral Reservation Fee](/fassets/minting#collateral-reservation-fee), which is not returned to the minter. - The minter loses the opportunity to mint and must restart the process. ### Payment Script After understanding the underlying payment timeframes, you can use the provided script to execute the payment. {FAssetsCreateXrpPayment} #### XRP Payment Script Breakdown 1. Install the `xrpl` package — it is not included in the Flare Hardhat Starter Kit by default. 2. Specify the correct constants from the reserve collateral script: - `AGENT_ADDRESS` - Agent's XRP Ledger address. - `AMOUNT_XRP` - XRP amount to send. - `PAYMENT_REFERENCE` - Payment reference from the reserve collateral script. 3. Create a client to connect to the XRP Ledger Testnet. 4. Load the sender wallet. 5. Construct the payment transaction. 6. Sign and submit the transaction. ## Generate Proof with Flare Data Connector Use the [FDC Payment](/fdc/guides/hardhat/payment) script to validate the XRP payment and generate a Merkle proof. ## Execute Minting Once the XRP payment is validated, you can retrieve the FDC proof from the [Data Availability Layer](/fdc/overview#data-availability-layer) and call the [`executeMinting`](/fassets/reference/IAssetManager#executeminting) function on the AssetManager contract. This script demonstrates how to retrieve the FDC proof and execute minting. {FAssetsExecuteMinting} ### Execute Minting Script Breakdown 1. Get environment variables. 2. Set the collateral reservation ID to the previously reserved minting request. 3. Set the FDC round ID to retrieve the proof. 4. Provide the FDC request data. 5. Import the Asset Manager contract artifact. 6. Define the function to prepare the FDC request. 7. Create a function to get the proof from the FDC. It sends a POST request to the [Flare Data Availability Layer](/fdc/overview#data-availability-layer) and returns a Merkle proof and attestation response from FDC. 8. Define the function to parse the events. 9. Retrieve the FDC proof from the Data Availability Layer. 10. Call the [`executeMinting`](/fassets/reference/IAssetManager#executeminting) function on the AssetManager contract and send a transaction to the Flare network to convert the attested XRP payment into FXRP (minting). 11. On a successful transaction call the `parseExecutemintingEvents` function to extract and log events [`RedemptionTicketCreated`](/fassets/reference/IAssetManagerEvents#redemptionticketcreated) and [`MintingExecuted`](/fassets/reference/IAssetManagerEvents#mintingexecuted). ## Video Tutorial ## Summary Now that you have successfully minted FAssets, you can use them in Flare dApps or transfer them to other users or smart contracts within the Flare ecosystem. :::tip[What's next] To continue your FAssets development journey, you can: - Learn how to [mint FAssets using the executor](/fassets/developer-guides/fassets-mint-executor). - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Mint FAssets using the Executor ## Overview This guide walks you through the complete process of minting FAssets (e.g., FXRP) on the Flare network using the [executor](/fassets/minting#executor-role). Minting FAssets is the process of wrapping, for instance, XRP from the XRP Ledger into an FAsset, enabling it to be used within the Flare blockchain ecosystem. :::info Executors are external actors that monitor pending minting requests and execute them by submitting payment proofs on-chain. They are incentivized for this task but have no special permissions—anyone can act as an executor. If no one executes in time, the minting request expires, and the process must be restarted. An executor can be a wallet or a DApp platform that facilitates minting and helps users automate the flow. ::: See the [Minting](/fassets/minting) overview for more details. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) ## Minting Process Steps The minting process involves the following steps: 1. Reserve collateral from a suitable agent and nominate an executor. 2. Send the underlying asset (e.g., XRP) to the agent. 3. Use [Flare Data Connector (FDC)](/fdc/overview) to generate a proof of payment. 4. Execute minting with the proof as a parameter to receive FAssets. ## Reserve Collateral Reserving collateral is the first step in the minting process. ### Minting Fees When reserving collateral, there are several fees that are paid. | Fee Type | Paid In | Details | | ------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Collateral Reservation Fee (CRF)** | Native tokens (FLR/SGB) | Compensates the agent and the collateral pool token (CPT) holders for locking their collateral during the minting process. Defined by governance as a percentage of the minted value. | | **Minting Fee** | Underlying currency (e.g., XRP for FXRP) | Main source of revenue for the agents and the CPT holders. Percentage of the minted amount (varies by agent). | | **Executor Fee (Optional)** | Native tokens (FLR/SGB) | Incentivizes the executor to process minting requests. Configurable fee denominated in FLR. | :::info[Collateral Reservation Fee (CRF)] If minting fails, the Collateral Reservation Fee is not returned to the minter. It is distributed to the agent and the pool in the same manner as the minting fee. ::: ### Reserve Collateral Script The following code demonstrates how to reserve collateral by calling the [`reserveCollateral`](/fassets/reference/IAssetManager#reservecollateral) function on the AssetManager contract. {FAssetsReserveCollateralWithExecutor} #### Collateral Reservation Script Breakdown 1. Define constants - `LOTS_TO_MINT`: Number of FAssets [lots](/fassets/minting#lots) to reserve. - `EXECUTOR_ADDRESS`: Executor address that will execute the minting process and provide the proof of underlying asset payment. 2. Get the AssetManager artifact 3. Retrieve and filter agents with enough free collateral and select the agent with the lowest fee and normal status. 4. Parse [`CollateralReserved`](/fassets/reference/IAssetManagerEvents#collateralreserved) event. 5. Start the minting reservation process at the script's entry point. 6. Get the asset manager artifact. 7. Call `findBestAgent` with the required number of lots. 8. Fetch agent metadata from [`getAgentInfo`](/fassets/reference/IAssetManager#getagentinfo) to get the agent's `feeBIPS`, which is used to calculate the collateral reservation fee. 9. Calculate the collateral reservation fee by calling [`collateralReservationFee`](/fassets/reference/IAssetManager#collateralreservationfee). 10. Set the executor fee the same as the collateral reservation fee to make this example simpler. 11. Reserve collateral from agent by calling [`reserveCollateral`](/fassets/reference/IAssetManager#reservecollateral) with the executor address. The executor fee is on top of the collateral reservation fee in native tokens. The fee is agreed between the minter and the executor. 12. Call `assetMintingDecimals` to determine the XRP token's decimal precision. 13. Parse the [`CollateralReserved`](/fassets/reference/IAssetManagerEvents#collateralreserved) event to get the collateral reservation ID and other important collateral reservation details. 14. Calculate the total XRP value required for payment. ## Send Payment on XRP Ledger The next step is to send the XRP Ledger payment to the agent. Before making the payment, it is important to understand the payment timeframes and constraints. ### Payment Timeframes The minting process has specific timeframes for underlying payments, controlled by [operational parameters](/fassets/operational-parameters): | Parameter | Unit | Purpose | | ----------------------------- | ------- | ---------------------------------------------------------------------------- | | `underlyingSecondsForPayment` | seconds | The minimum time allowed for a minter to pay on the underlying chain | | `underlyingBlocksForPayment` | blocks | The number of underlying blocks during which the minter can make the payment | These parameters work together as a dual safety mechanism that ensures payment happens within a reasonable number of blocks and guarantees payment occurs within a reasonable amount of time. Both constraints must be satisfied for the payment to be considered valid. #### Payment Deadline Calculation When you reserve collateral, the system emits the following values in the [`CollateralReserved`](/fassets/reference/IAssetManagerEvents#collateralreserved) event: - `lastUnderlyingBlock`: The final block number for a valid payment. - `lastUnderlyingTimestamp`: The deadline timestamp for payment. Valid payments must occur **before both the last block AND the last timestamp**. #### Payment Failure Handling If the minter [fails to pay on the underlying chain](/fassets/minting#payment-failure) within the required timeframe: - The agent must prove [nonpayment](/fdc/attestation-types/referenced-payment-nonexistence) using the [Flare Data Connector](/fdc/overview). - After nonpayment is proved, the agent's collateral that was reserved is released. - The agent receives the [Collateral Reservation Fee](/fassets/minting#collateral-reservation-fee), which is not returned to the minter. - The minter loses the opportunity to mint and must restart the process. ### Payment Script After understanding the underlying payment timeframes, you can use the provided script to execute the payment. {FAssetsCreateXrpPayment} #### XRP Payment Script Breakdown 1. Install the `xrpl` package — it's not included in the Flare Hardhat Starter Kit by default. 2. Specify the correct constants from the reserve collateral script: - `AGENT_ADDRESS` - Agent's XRP Ledger address. - `AMOUNT_XRP` - XRP amount to send. - `PAYMENT_REFERENCE` - Payment reference from the the reserve collateral script. 3. Create a client to connect to the XRP Ledger Testnet. 4. Load the sender's wallet. 5. Construct the payment transaction. 6. Sign and submit the transaction. ## Generate Proof with FDC The executor will use the FDC's [Payment attestastion type](/fdc/guides/hardhat/payment) to validate the XRP payment and generate a Merkle proof. ## Execute Minting Once the XRP payment is validated, executor can retrieve the FDC proof from the [Data Availability Layer](/fdc/overview#data-availability-layer) and call the [`executeMinting`](/fassets/reference/IAssetManager#executeminting) function on the AssetManager contract. Once the minting is executed, the executor will receive the executor fee in native tokens. If the executor fails to execute in time, the request expires, and minting must be restarted. This script demonstrates how to retrieve the FDC proof and execute minting. {FAssetsExecuteMinting} ### Execute Minting Script Breakdown 1. Get environment variables. 2. Resolve the FXRP AssetManager address dynamically on Coston2 by calling `getAssetManagerFXRP()` from the registry helper rather than hard-coding it. 3. Set the collateral reservation ID to the previously reserved minting request. 4. Set the FDC round ID to retrieve the proof. 5. Prepare the FDC request payload data. 6. Create a function to get the proof from the FDC. It sends a POST request to the [Flare Data Availability Layer](/fdc/overview#data-availability-layer) and returns a Merkle proof and attestation response from FDC. 7. Retrieve the FDC proof from the Data Availability Layer. 8. Call the [`executeMinting`](/fassets/reference/IAssetManager#executeminting) function on the AssetManager contract and send a transaction to the Flare network to convert the attested XRP payment into FXRP (minting). 9. On a successful transaction call `parseEvents` to extract and log events [`RedemptionTicketCreated`](/fassets/reference/IAssetManagerEvents#redemptionticketcreated) and [`MintingExecuted`](/fassets/reference/IAssetManagerEvents#mintingexecuted). ## Video Tutorial ## Summary Now that you have successfully minted FAssets using the executor, you can use them in Flare dApps or transfer them to other users or smart contracts within the Flare ecosystem. :::tip[What's next] To continue your FAssets development journey, you can: - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Redeem FXRP by Amount ## Overview This guide walks you through redeeming FXRP using [`redeemAmount`](/fassets/reference/IAssetManager#redeemamount) on the `AssetManagerFXRP` contract. Unlike the previous [`redeem`](/fassets/reference/IAssetManager#redeem) guide, `redeemAmount` accepts an arbitrary amount instead of whole lots. The complete runnable example is available in the [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/fassets/redeem-amount.ts) repository. ## Prerequisites - An EVM wallet with FXRP. - Native tokens on the same wallet to cover the gas fees. - An XRPL address to receive the redeemed XRP. ## Redeem Amount Script {FAssetsRedeemAmount} ## Code Breakdown 1. Set `REDEEM_AMOUNT_UBA` to the amount of FXRP to redeem in UBA (underlying base unit). 2. Set `REDEEMER_UNDERLYING_ADDRESS_STRING` to the XRPL address that will receive the redeemed XRP from the agent. 3. `EXECUTOR_ZERO_ADDRESS` is used because no executor is appointed in this example. Pass a real address to delegate default-handling to an executor. 4. Resolve the `AssetManagerFXRP` address through the [Flare Contract Registry](/network/guides/flare-contracts-registry) using [`getContractAddressByName`](/network/guides/flare-contracts-registry). 5. Read [`minimumRedeemAmountUBA`](/fassets/reference/IAssetManager#minimumredeemamountuba) and check that the requested amount is at least the protocol minimum. 6. Simulate the [`redeemAmount`](/fassets/reference/IAssetManager#redeemamount) call to validate the arguments and produce a signed request payload. 7. Submit the redemption request transaction. 8. Wait for the transaction receipt. 9. Decode [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) events from the receipt logs with the `parseEventLogs` function and select the one whose `redeemer` matches the caller. A single call may emit multiple `RedemptionRequested` events when multiple agents fulfill the request. ## Important Notes - **`minimumRedeemAmountUBA` is enforced on-chain.** Requests below the threshold revert. - **The redemption may be partial.** If the request requires too many redemption tickets, only part is fulfilled, and the remaining tickets are returned via [`RedemptionAmountIncomplete`](/fassets/reference/IAssetManagerEvents#redemptionamountincomplete). Call `redeemAmount` again for the remainder. - **Multiple agents may pay.** A single `redeemAmount` call can produce several [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) events — one per agent serving the request. Track each `requestId` for the agent's underlying-chain payment. - **Agents have a deadline to pay.** If an agent fails to pay within the redemption window, start the [redemption default process](/fassets/developer-guides/fassets-redemption-default). - **Use `redeemWithTag` for exchange deposits.** When the destination XRPL address requires a destination tag, call [`redeemWithTag`](/fassets/reference/IAssetManager#redeemwithtag) instead. :::tip[What's next] To continue your FAssets development journey, you can: - Redeem with tag with [`redeemWithTag`](/fassets/developer-guides/fassets-redeem-with-tag). - Handle non-paying agents in [Monitor Redemptions & Execute Defaults](/fassets/developer-guides/fassets-redemption-default). - Mint without a tag using [memo-based direct minting](/fassets/developer-guides/fassets-direct-minting) or [direct minting with tag](/fassets/developer-guides/fassets-direct-minting-tag). ::: --- ## List FAssets Agents ## Overview In this guide, you will learn how to list FAssets agents using the [AssetManager](/fassets/reference/IAssetManager) smart contract. ## Prerequisites - Basic understanding of the [FAssets system](/fassets/overview). - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package. ## List FAssets Agents The [AssetManager](/fassets/reference/IAssetManager) smart contract provides these functions to list FAssets agents: - [`getAllAgents`](/fassets/reference/IAssetManager#getallagents): Returns all agents in the system, including both available and unavailable agents, with basic information. - [`getAvailableAgentsList`](/fassets/reference/IAssetManager#getavailableagentslist): Returns only available agents (agents accepting new minting requests) with basic information. - [`getAvailableAgentsDetailedList`](/fassets/reference/IAssetManager#getavailableagentsdetailedlist): Returns only available agents with comprehensive details, including collateral ratios, fees, and vault status. ### Function Parameters All of these functions take two parameters: - `_start`: First index to return from the agents list. - `_end`: End index to return from the agents list (exclusive). And return two values: - `_agents`: Array of agent information structs. - `_totalLength`: Total number of agents in the system (used for pagination). :::tip Index Parameters All of these functions use array-like slicing: - `start`: Inclusive start index (0-based). - `end`: Exclusive end index. For example, `getAllAgents(0, 3)` returns agents at indices 0, 1, and 2. ::: ## Example This example demonstrates how to use the [`getAvailableAgentsList`](/fassets/reference/IAssetManager#getavailableagentslist) function to list available agents in chunks. The chunking approach prevents RPC timeouts when dealing with many agents by fetching data in smaller, manageable batches. {FAssetsListAgents} ### How the Code Works The script follows these steps: 1. **Get Asset Manager Contract**: Retrieve the FAssets FXRP Asset Manager contract instance using the helper function `getAssetManagerFXRP()` from the Flare periphery contracts package. 2. **Define Chunk Size**: Set `chunkSize` to `3`, determining how many agents to fetch per request. Using smaller chunks helps avoid RPC timeout issues when dealing with many agents. You can adjust this value based on your network conditions and RPC provider limits. 3. **Fetch First Chunk**: Call `getAvailableAgentsList(0, chunkSize)` which: - Returns the first three agents (indices 0, 1, 2). - Provides the `_totalLength` property showing the total number of agents in the system. 4. **Iterate Through Remaining Agents**: Loop through the remaining agents in chunks: - Start from `offset = chunkSize` (3) and increment by `chunkSize` each iteration. - Calculate `endIndex = Math.min(offset + chunkSize, totalLength)` to ensure we do not exceed the total length. - Fetch each chunk using `getAvailableAgentsList(offset, endIndex)`. - Display the agents in each chunk for processing or analysis. :::info A similar approach can be used to list all agents using the [`getAllAgents`](/fassets/reference/IAssetManager#getallagents) and [`getAvailableAgentsDetailedList`](/fassets/reference/IAssetManager#getavailableagentsdetailedlist) functions. ::: ### Run the Script To run the script, use the following command: ```bash yarn hardhat run scripts/fassets/listAgents.ts --network coston2 ``` To use the script, replace `coston2` with the network you are using. The script will output information about the available agents on the [Coston2](/network/overview) network. ## Summary This guide covered: - **Agent List Functions**: Three different methods to retrieve agent information with varying levels of detail. - **Pagination Strategy**: How to use chunking to retrieve large agent lists without overwhelming RPC endpoints efficiently. - **Practical Implementation**: A complete TypeScript example demonstrating agent list retrieval with proper pagination and error handling considerations. :::tip Next Steps To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint) - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) - Explore [FAssets system settings](/fassets/operational-parameters) ::: --- ## Developer Guides Step-by-step guides for integrating and building with the [FAssets system](/fassets/overview) on Flare. ## Getting Started Essential guides to begin working with FAssets. ## Minting FAssets Guides for minting FAssets from underlying assets. ## Redeeming FAssets Guides for redeeming FAssets back to underlying assets. ## FXRP Token Interactions Guides for interacting with FXRP token. See also the [FXRP overview page](/fxrp/overview) for more FXRP resources. ## Agent Information Guides for reading and working with agent data. :::tip[What's next] - Explore [FXRP](/fxrp/overview) and discover what you can do with the FAsset representation of XRP. - Read the [FAssets overview](/fassets/overview) to understand how the system works. - Learn how to earn yield on your FXRP with [Firelight Vaults](/fxrp/firelight). ::: --- ## IAgentOwnerRegistry Command line reference for interacting with FAssets `AgentOwnerRegistry` contract. Sourced from `IAgentOwnerRegistry.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/IAgentOwnerRegistry.sol). --- ## Overview The `IAgentOwnerRegistry` interface manages agent owner information and work address mappings in the FAssets system. ## Agent Information Functions ### `getAgentName` Returns the agent owner's name. Parameters: - `_managementAddress`: The agent owner's management address Returns: - `string memory`: The agent owner's name ```solidity function getAgentName(address _managementAddress) external view returns (string memory); ``` ### `getAgentDescription` Returns the agent owner's description. Parameters: - `_managementAddress`: The agent owner's management address Returns: - `string memory`: The agent owner's description ```solidity function getAgentDescription(address _managementAddress) external view returns (string memory); ``` ### `getAgentIconUrl` Returns the URL of the agent owner's icon. Parameters: - `_managementAddress`: The agent owner's management address Returns: - `string memory`: The URL of the agent owner's icon ```solidity function getAgentIconUrl(address _managementAddress) external view returns (string memory); ``` ### `getAgentTermsOfUseUrl` Returns the URL of the agent's page with terms of use. Parameters: - `_managementAddress`: The agent owner's management address Returns: - `string memory`: The URL of the agent's terms of use page ```solidity function getAgentTermsOfUseUrl(address _managementAddress) external view returns (string memory); ``` ## Address Mapping Functions ### `getWorkAddress` Gets the unique work address for the given management address. Parameters: - `_managementAddress`: The agent owner's management address Returns: - `address`: The corresponding work address ```solidity function getWorkAddress(address _managementAddress) external view returns (address); ``` ### `getManagementAddress` Gets the unique management address for the given work address. Parameters: - `_workAddress`: The agent owner's work address Returns: - `address`: The corresponding management address ```solidity function getManagementAddress(address _workAddress) external view returns (address); ``` --- ## IAgentPing `IAgentPing` exposes a simple ping/response protocol for verifying that an FAssets agent's off-chain bot is alive. Users (or anyone) call `agentPing(agentVault, query)` to emit a `AgentPing` event; the bot listens for these events and replies with `agentPingResponse(agentVault, query, response)`, which emits an `AgentPingResponse` event identifying the owner address. Sourced from `IAgentPing.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/IAgentPing.sol). ## Functions ### agentPing ```solidity function agentPing(address _agentVault, uint256 _query) external; ``` Emits an `AgentPing` event addressed at `_agentVault`. `_query` is an off-chain-defined identifier that helps the bot decide which kind of response is required (and whether the request is worth responding to at all). ### agentPingResponse ```solidity function agentPingResponse( address _agentVault, uint256 _query, string calldata _response ) external; ``` Called by the agent owner address in response to an `AgentPing`. Emits `AgentPingResponse(agentVault, owner, query, response)`. ## Events ### AgentPing ```solidity event AgentPing(address indexed agentVault, address indexed sender, uint256 query); ``` ### AgentPingResponse ```solidity event AgentPingResponse(address indexed agentVault, address indexed owner, uint256 query, string response); ``` --- ## IAssetManager Command line reference for interacting with FAssets `AssetManager` contract. Sourced from `IAssetManager.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/IAssetManager.sol). --- ## Information ### `getSettings` Returns the complete asset manager settings as the [`AssetManagerSettings`](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/data/AssetManagerSettings.sol) struct. You can find detailed explanations of each parameter in the [FAssets Operational Parameters](/fassets/operational-parameters/#asset-manager-operational-parameters) documentation. ```solidity function getSettings() external view returns (AssetManagerSettings.Data memory); ``` #### `getAgentInfo` Returns detailed information about an agent as the [`AgentInfo`](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/data/AgentInfo.sol) struct. Need to provide the agent vault address. ```solidity function getAgentInfo(address _agentVault) external view returns (AgentInfo.Info memory); ``` ### `getCollateralTypes` Returns the list of all available and deprecated tokens used for collateral types in the FAssets system. Returns: - [`CollateralType.Data`](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/data/CollateralType.sol): Array of collateral type data structures containing information about each supported collateral token. ```solidity function getCollateralTypes() external view returns (CollateralType.Data[] memory); ``` ### `collateralReservationFee` Returns the collateral reservation fee amount. Parameters: - `_lots`: The number of lots for which to reserve collateral. Returns: - `_reservationFeeNATWei`: The amount of reservation fee in NAT wei. ```solidity function collateralReservationFee( uint256 _lots ) external view returns (uint256 _reservationFeeNATWei); ``` ### `collateralReservationInfo` Returns the data about the collateral reservation for an ongoing minting. Note: once the minting is executed or defaulted, the collateral reservation is deleted and this method fails. Parameters: - `_collateralReservationId`: The collateral reservation ID, as used for executing or defaulting the minting. ```solidity function collateralReservationInfo(uint256 _collateralReservationId) external view returns (CollateralReservationInfo.Data memory); ``` ### `fAsset` Returns the FAsset token contract (ERC-20) that is managed by this asset manager instance. Returns: - `IERC20`: The address of the FAsset token contract. ```solidity function fAsset() external view returns (IERC20); ``` ## Direct Minting Settings ### `directMintingPaymentAddress` Gets the XRPL address of the core vault. This is the payment address to which the underlying assets must be sent for direct minting. ```solidity function directMintingPaymentAddress() external view returns (string memory); ``` ### `getDirectMintingMinimumFeeUBA` Returns the minimum direct minting fee in UBA. ```solidity function getDirectMintingMinimumFeeUBA() external view returns (uint256); ``` ### `getDirectMintingFeeBIPS` Returns the direct minting fee in BIPS. ```solidity function getDirectMintingFeeBIPS() external view returns (uint256); ``` ### `getDirectMintingExecutorFeeUBA` Returns the direct minting executor fee in UBA. ```solidity function getDirectMintingExecutorFeeUBA() external view returns (uint256); ``` ### `getDirectMintingOthersCanExecuteAfterSeconds` Returns the length of the preferred-executor exclusivity window for direct minting, in seconds. When a direct minting specifies a preferred executor — either through a tag bound on the [`IMintingTagManager`](/fassets/reference/IMintingTagManager) or through the 48-byte [memo format](/fassets/direct-minting#memo-field) — only that executor may finalize the minting until this number of seconds has elapsed since the underlying payment. After the window expires, anyone can call [`executeDirectMinting`](/fassets/reference/IAssetManager#executedirectminting) to finalize the request. If the minting is delayed by [rate limits](/fassets/direct-minting#rate-limits), the exclusivity window restarts from the [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) event's `executionAllowedAt` timestamp. ```solidity function getDirectMintingOthersCanExecuteAfterSeconds() external view returns (uint256); ``` ### `getDirectMintingHourlyLimitUBA` Returns the hourly direct minting limit in UBA. ```solidity function getDirectMintingHourlyLimitUBA() external view returns (uint256); ``` ### `getDirectMintingDailyLimitUBA` Returns the daily direct minting limit in UBA. ```solidity function getDirectMintingDailyLimitUBA() external view returns (uint256); ``` ### `getDirectMintingHourlyLimiterState` Returns the raw state of the hourly direct-minting [rate limiter](/fassets/direct-minting#rate-limits). Returns: - `_windowStartTimestamp`: Unix timestamp when the current hourly window started, snapped to a multiple of `windowSizeSeconds` (UTC hour boundaries). - `_mintedInCurrentWindow`: Amount minted in the current window, denominated in AMG. Multiply by [`assetMintingGranularityUBA`](#assetmintinggranularityuba) to convert to UBA. The value is only updated on the next limiter write, so consumers that need a live view should advance the window off-chain — see the [Check Direct Minting Limits guide](/fassets/developer-guides/fassets-direct-minting-limits). ```solidity function getDirectMintingHourlyLimiterState() external view returns (uint64 _windowStartTimestamp, uint64 _mintedInCurrentWindow); ``` ### `getDirectMintingDailyLimiterState` Returns the raw state of the daily direct-minting [rate limiter](/fassets/direct-minting#rate-limits). Returns: - `_windowStartTimestamp`: Unix timestamp when the current daily window started, snapped to 00:00 UTC. - `_mintedInCurrentWindow`: Amount minted in the current window, denominated in AMG. Multiply by [`assetMintingGranularityUBA`](#assetmintinggranularityuba) to convert to UBA. The value is only updated on the next limiter write, so consumers that need a live view should advance the window off-chain — see the [Check Direct Minting Limits guide](/fassets/developer-guides/fassets-direct-minting-limits). ```solidity function getDirectMintingDailyLimiterState() external view returns (uint64 _windowStartTimestamp, uint64 _mintedInCurrentWindow); ``` ### `getDirectMintingsUnblockUntilTimestamp` Returns the Unix timestamp until which the direct-minting [rate limiter](/fassets/direct-minting#rate-limits) is bypassed. While `block.timestamp` is below this value, the hourly and daily caps are not enforced and queued mintings can execute without delay. Governance sets this via `unblockDirectMintingsUntil` to drain a backlog after manual review; the value is `0` when the limiter is active. ```solidity function getDirectMintingsUnblockUntilTimestamp() external view returns (uint256); ``` ### `getDirectMintingLargeMintingThresholdUBA` Returns the threshold above which direct minting is considered large, in UBA. ```solidity function getDirectMintingLargeMintingThresholdUBA() external view returns (uint256); ``` ### `getDirectMintingLargeMintingDelaySeconds` Returns the delay in seconds applied to large direct mintings. ```solidity function getDirectMintingLargeMintingDelaySeconds() external view returns (uint256); ``` ### `assetMintingGranularityUBA` Returns the asset minting granularity — the smallest unit of FAsset stored internally within this asset manager instance. The direct-minting [rate limiter](/fassets/direct-minting#rate-limits) stores `mintedInCurrentWindow` as `uint64` AMG (asset minting granularity) for cheap on-chain storage. Multiplying the limiter state by `assetMintingGranularityUBA` rebases it into UBA so it can be compared against the UBA-denominated hourly and daily caps. ```solidity function assetMintingGranularityUBA() external view returns (uint256); ``` ### `getDirectMintingFeeReceiver` Returns the address that receives direct minting fees. ```solidity function getDirectMintingFeeReceiver() external view returns (address); ``` ## Redeem With Tag Settings ### `getMintingTagManager` Returns the minting tag manager contract address. To interact with it, use the [IMintingTagManager](/fassets/reference/IMintingTagManager) reference. ```solidity function getMintingTagManager() external view returns (address); ``` ## Agents ### `getAllAgents` Returns the list of all agents. Parameters: - `_start`: First index to return from the available agent's list. - `_end`: End index (one above last) to return from the available agent's list. Returns: - `_agents`: The list of agents between the `_start` and `_end` indexes. - `_totalLength`: The total length of the all agents list. ```solidity function getAllAgents(uint256 _start, uint256 _end) external view returns (address[] memory _agents, uint256 _totalLength); ``` ### `getAvailableAgentsList` Returns the list of available agents. Parameters: - `_start`: Starting index to return from the available agent's list (inclusive). - `_end`: Ending index to return from the available agent's list (exclusive). Returns: - `_agents`: The list of available agents between the `_start` and `_end` indexes. - `_totalLength`: The total length of the available agents list. ```solidity function getAvailableAgentsList(uint256 _start, uint256 _end) external view returns (address[] memory _agents, uint256 _totalLength); ``` ### `getAvailableAgentsDetailedList` Returns the list of available agents with extra information about agents like fee, minimum collateral ratio and available collateral (in lots). Parameters: - `_start`: Starting index to return from the available agent's list (inclusive). - `_end`: Ending index to return from the available agent's list (exclusive). Returns: - `_agents`: The list of available agents with extra information. - `_totalLength`: The total length of the available agents list. ```solidity function getAvailableAgentsDetailedList(uint256 _start, uint256 _end) external view returns (AvailableAgentInfo.Data[] memory _agents, uint256 _totalLength); ``` ## Redemption Queue ### `redemptionQueue` Returns the redemption queue in the form of an array of [`RedemptionTicketInfo`](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/data/RedemptionTicketInfo.sol) structs. Parameters: - `_firstRedemptionTicketId`: The ticket id to start listing from; if `0`, starts from the beginning. - `_pageSize`: The maximum number of redemption tickets to return. Returns: - `_queue`: The (part of) redemption queue; maximum length is \_pageSize. - `_nextRedemptionTicketId`: Works as a cursor - if the `_pageSize` is reached and there are more tickets, it is the first ticket id not returned; if the end is reached, it is 0. ```solidity function redemptionQueue( uint256 _firstRedemptionTicketId, uint256 _pageSize ) external view returns (RedemptionTicketInfo.Data[] memory _queue, uint256 _nextRedemptionTicketId); ``` ### `agentRedemptionQueue` Returns the redemption queue for specific agent in the form of an array of [`RedemptionTicketInfo`](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/data/RedemptionTicketInfo.sol) structs. Parameters: - `_agentVault`: The agent vault address of the queried agent. - `_firstRedemptionTicketId`: The ticket id to start listing from; if `0`, starts from the beginning. - `_pageSize`: The maximum number of redemption tickets to return. Returns: - `_queue`: The (part of) the redemption queue; maximum length is \_pageSize. - `_nextRedemptionTicketId`: Works as a cursor - if the` _pageSize` is reached and there are more tickets, it is the first ticket id not returned; if the end is reached, it is 0. ```solidity function agentRedemptionQueue( address _agentVault, uint256 _firstRedemptionTicketId, uint256 _pageSize ) external view returns (RedemptionTicketInfo.Data[] memory _queue, uint256 _nextRedemptionTicketId); ``` ## Collateral Reservation ### `reserveCollateral` Reserves collateral for minting FAssets. Before paying underlying assets for minting, the minter must reserve collateral and pay a collateral reservation fee. Parameters: - `_agentVault`: Agent vault address. - `_lots`: Number of lots for which to reserve collateral. - `_maxMintingFeeBIPS`: Maximum minting fee (BIPS) that can be charged by the agent - best practice is to copy the current agent's published fee; used to prevent agent from front-running reservation request and increasing fee. - `_executor`: Account that is allowed to execute minting (besides minter and agent). ```solidity function reserveCollateral( address _agentVault, uint256 _lots, uint256 _maxMintingFeeBIPS, address payable _executor ) external payable; ``` ## Execute Minting ### `executeMinting` After obtaining proof of underlying payment, the minter calls this method to finish the minting and collect the minted FAssets. Note: May only be called by: - The minter (creator of the collateral reservation request). - The executor appointed by the minter. - The agent owner (owner of the agent vault in the collateral reservation). Parameters: - `_payment`: Proof of the underlying payment (must contain exact `value + fee` amount and correct payment reference). - `_collateralReservationId`: Collateral reservation ID. ```solidity function executeMinting( IPayment.Proof calldata _payment, uint256 _collateralReservationId ) external nonReentrant; ``` ## Execute Direct Minting ### `executeDirectMinting` Finalizes [direct minting](/fassets/direct-minting): mints FAssets to the recipient encoded in the XRPL payment (destination tag or memo), using an attestation that the underlying was paid to the FAsset Core Vault. Who may call, executor preference and timeouts, fees, and rate limits are described in [Direct minting](/fassets/direct-minting) and in [`getDirectMintingOthersCanExecuteAfterSeconds`](#getdirectmintingotherscanexecuteafterseconds). Parameters: - `_payment`: XRP payment proof data (`IXRPPayment.Proof`) for the FDC-verified payment that satisfies the direct-minting rules. ```solidity function executeDirectMinting(IXRPPayment.Proof calldata _payment) external payable; ``` ## Redemption ### `redeem` Redeem number of lots of FAssets. Returns the actual redeemed amount. Parameters: - `_lots`: Number of lots to redeem. - `_redeemerUnderlyingAddressString`: The address to which the agent must transfer underlying amount. - `_executor`: The account that is allowed to execute redemption default (besides redeemer and agent). ```solidity function redeem( uint256 _lots, string memory _redeemerUnderlyingAddressString, address payable _executor ) external payable returns (uint256 _redeemedAmountUBA); ``` ### `redeemAmount` Redeem up to `_amountUBA` FAssets, like [`redeem`](/fassets/reference/IAssetManager#redeem), but accepts an arbitrary amount in UBA instead of whole lots and does not require a destination tag. The `redeemAmount` function enforces a minimum redemption size `minimumRedeemAmount`. In some cases, not all sent FAssets can be redeemed (for example, if there are insufficient tickets or the maximum ticket limit has been reached). In that case, only part of the approved assets are burned and redeemed, and the redeemer can call this method again for the remaining amount. The [`RedemptionAmountIncomplete`](/fassets/reference/IAssetManagerEvents#redemptionamountincomplete) event is emitted with the remaining amount. Parameters: - `_amountUBA`: Amount of redeemer's FAssets that will be burned (this is not the received amount; redemption fee is subtracted). - `_redeemerUnderlyingAddressString`: Address to which the agent must transfer the underlying amount. - `_executor`: Account that can execute redemption default (besides redeemer and agent). Returns: - `_redeemedAmountUBA`: Actual redeemed amount; may be less than requested when ticket availability or ticket limits apply. ```solidity function redeemAmount( uint256 _amountUBA, string memory _redeemerUnderlyingAddressString, address payable _executor ) external payable returns (uint256 _redeemedAmountUBA); ``` ### `redeemWithTag` Redeem up to `_amountUBA` FAssets and require a destination tag in the redemption XRPL payment. In some cases, not all sent FAssets can be redeemed (for example, if there are insufficient tickets or the maximum ticket limit has been reached). In that case, only part of the approved assets are burned and redeemed, and the redeemer can call this method again for the remaining amount. The [`RedemptionAmountIncomplete`](/fassets/reference/IAssetManagerEvents#redemptionamountincomplete) event is emitted with the remaining amount. Parameters: - `_amountUBA`: Amount of redeemer's FAssets that will be burned (this is not the received amount; redemption fee is subtracted). - `_redeemerUnderlyingAddressString`: Address to which the agent must transfer the underlying amount. - `_executor`: Account that can execute redemption default (besides redeemer and agent). - `_destinationTag`: Destination tag required in redemption payment (XRP only; must fit in 32 bits for now). Returns: - `_redeemedAmountUBA`: Actual redeemed amount; may be less than requested when ticket availability or ticket limits apply. ```solidity function redeemWithTag( uint256 _amountUBA, string memory _redeemerUnderlyingAddressString, address payable _executor, uint256 _destinationTag ) external payable returns (uint256 _redeemedAmountUBA); ``` ### `minimumRedeemAmountUBA` Minimum amount in UBA for redemption with tag. Redemption requests with smaller amounts are rejected. This governance-configured minimum applies to both [`redeemAmount`](/fassets/reference/IAssetManager#redeemamount) and [`redeemWithTag`](/fassets/reference/IAssetManager#redeemwithtag) functions. ```solidity function minimumRedeemAmountUBA() external view returns (uint256); ``` ### `redemptionPaymentDefault` If the agent fails to transfer the redeemed underlying assets in a timely manner, the redeemer or appointed executor can invoke this method and receive payment in collateral. The agent can also call default if the redeemer is unresponsive to payout the redeemer and free the remaining collateral. Parameters: - `_proof`: Proof that the agent did not pay with correct payment reference on the underlying chain. - `_redemptionRequestId`: ID of an existing redemption request. ```solidity function redemptionPaymentDefault( IReferencedPaymentNonexistence.Proof calldata _proof, uint256 _redemptionRequestId ) external; ``` ## Core Vault Settings Reference for managing and interacting with FAssets `ICoreVaultSettings` contract which is inherited by the `IAssetManager` contract. Sourced from `ICoreVaultSettings.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/ICoreVaultClientSettings.sol). --- ### `getCoreVaultManager` Returns the core vault manager address. To interact with the Core Vault manager reference the [Core Vault Manager](/fassets/reference/ICoreVaultManager) contract. ```solidity function getCoreVaultManager() external view returns (address); ``` ### `getCoreVaultDonationTag` Returns the destination tag used for donations to the core vault payment address. ```solidity function getCoreVaultDonationTag() external view returns (uint256); ``` ### `getCoreVaultMinimumAmountLeftBIPS` Returns the minimum amount of minting left on agent's address after transfer to core vault. ```solidity function getCoreVaultMinimumAmountLeftBIPS() external view returns (uint256); ``` #### `getCoreVaultTransferTimeExtensionSeconds` Returns the extra time for an agent's transfer to the core vault. ```solidity function getCoreVaultTransferTimeExtensionSeconds() external view returns (uint256); ``` ### `getCoreVaultTransferFeeBIPS` Returns the fee paid by agent for transfer to the core vault. ```solidity function getCoreVaultTransferFeeBIPS() external view returns (uint256); ``` ### `getCoreVaultMinimumRedeemLots` Returns the minimum number of lots that a direct redemption from core vault can take. ```solidity function getCoreVaultMinimumRedeemLots() external view returns (uint256); ``` ### `getCoreVaultRedemptionFeeBIPS` Returns the fee paid by the redeemer for direct redemptions from the core vault. ```solidity function getCoreVaultRedemptionFeeBIPS() external view returns (uint256); ``` --- ## IAssetManagerController Command line reference for interacting with the FAssets `AssetManagerController` contract. Sourced from `IAssetManagerController.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/IAssetManagerController.sol). ## Functions ### `getAssetManagers` Returns the list of all asset managers managed by this controller. To interact with the asset manager, follow [IAssetManager](/fassets/reference/IAssetManager) reference. ```solidity function getAssetManagers() external view returns (IAssetManager[] memory); ``` ### `assetManagerExists` Check if the asset manager is managed by this asset manager controller. Parameters: - `_assetManager`: Address of the asset manager Returns: - `bool`: `true` if the asset manager is managed by this controller, `false` otherwise. ```solidity function assetManagerExists(address _assetManager) external view returns (bool); ``` --- ## IAssetManagerEvents `IAssetManagerEvents` is an interface that defines the events emitted by the [`IAssetManager`](/fassets/reference/IAssetManager) contract. Sourced from `IAssetManagerEvents.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/IAssetManagerEvents.sol). ## Event Categories The events are organized into the following categories: - **[Minting Events](#minting-events)** - Events related to the FAssets minting process. - **[Redemption Events](#redemption-events)** - All events related to the FAssets redemption process, including requests, tickets, outcomes, and failures. ## Minting Events Events related to the FAssets minting process, including collateral reservation, execution, and defaults. ### `CollateralReserved` Emitted when a minter has reserved collateral, paid the reservation fee, and is expected to pay the underlying funds. The agent's collateral is reserved at this point. Parameters: - `agentVault`: Address of the agent vault - `minter`: Address of the minter - `collateralReservationId`: ID of the collateral reservation - `valueUBA`: Value in the underlying base amount - `feeUBA`: Fee in the underlying base amount - `firstUnderlyingBlock`: First block number on the underlying chain - `lastUnderlyingBlock`: Last block number on the underlying chain - `lastUnderlyingTimestamp`: Last timestamp on the underlying chain - `paymentAddress`: Address for payment - `paymentReference`: Reference for payment - `executor`: Address of the executor - `executorFeeNatWei`: Fee for the executor in NAT wei ```solidity event CollateralReserved( address indexed agentVault, address indexed minter, uint256 indexed collateralReservationId, uint256 valueUBA, uint256 feeUBA, uint256 firstUnderlyingBlock, uint256 lastUnderlyingBlock, uint256 lastUnderlyingTimestamp, string paymentAddress, bytes32 paymentReference, address executor, uint256 executorFeeNatWei ); ``` ### `CollateralReservationRejected` Emitted when an agent rejects the collateral reservation request due to the minter's identity. The reserved collateral is released. Parameters: - `agentVault`: Address of the agent vault - `minter`: Address of the minter - `collateralReservationId`: ID of the collateral reservation ```solidity event CollateralReservationRejected( address indexed agentVault, address indexed minter, uint256 indexed collateralReservationId ); ``` ### `CollateralReservationCancelled` Emitted when a minter cancels the collateral reservation request due to agent inactivity. The reserved collateral is released. Parameters: - `agentVault`: Address of the agent vault - `minter`: Address of the minter - `collateralReservationId`: ID of the collateral reservation ```solidity event CollateralReservationCancelled( address indexed agentVault, address indexed minter, uint256 indexed collateralReservationId ); ``` ### `MintingExecuted` Emitted when a minter has successfully paid the underlying funds in time and received the FAssets. The agent's collateral is locked at this point. Parameters: - `agentVault`: Address of the agent vault - `collateralReservationId`: ID of the collateral reservation - `mintedAmountUBA`: Amount of FAssets minted in the underlying base amount - `agentFeeUBA`: Fee paid to the agent in the underlying base amount - `poolFeeUBA`: Fee paid to the pool in the underlying base amount ```solidity event MintingExecuted( address indexed agentVault, uint256 indexed collateralReservationId, uint256 mintedAmountUBA, uint256 agentFeeUBA, uint256 poolFeeUBA ); ``` ### `DirectMintingExecuted` Emitted when a direct minting payment is confirmed and FAssets are minted to the target address. Parameters: - `transactionId`: Identifier of the direct minting transaction - `targetAddress`: Address that receives the minted FAssets - `executor`: Address of the executor that submitted the minting proof - `mintedAmountUBA`: Amount of FAssets minted in the underlying base amount - `mintingFeeUBA`: Minting fee in the underlying base amount - `executorFeeUBA`: Fee paid to the executor in the underlying base amount ```solidity event DirectMintingExecuted( bytes32 transactionId, address targetAddress, address executor, uint256 mintedAmountUBA, uint256 mintingFeeUBA, uint256 executorFeeUBA ); ``` ### `DirectMintingDelayed` Emitted when a direct minting is throttled by rate limits and its execution is postponed. Parameters: - `transactionId`: Identifier of the direct minting transaction - `amount`: Amount being minted in the underlying base amount - `executionAllowedAt`: Earliest timestamp at which the minting can be executed ```solidity event DirectMintingDelayed( bytes32 transactionId, uint256 amount, uint256 executionAllowedAt ); ``` ### `MintingPaymentDefault` Emitted when a minter fails to pay the underlying funds in time. The collateral reservation fee is paid to the agent, and the reserved collateral is released. Parameters: - `agentVault`: Address of the agent vault - `minter`: Address of the minter - `collateralReservationId`: ID of the collateral reservation - `reservedAmountUBA`: Amount that was reserved in the underlying base amount ```solidity event MintingPaymentDefault( address indexed agentVault, address indexed minter, uint256 indexed collateralReservationId, uint256 reservedAmountUBA ); ``` ### `CollateralReservationDeleted` Emitted when both the minter and agent fail to present any proof within the attestation time window, and the agent calls `unstickMinting` to release the reserved collateral. Parameters: - `agentVault`: Address of the agent vault - `minter`: Address of the minter - `collateralReservationId`: ID of the collateral reservation - `reservedAmountUBA`: Amount that was reserved in the underlying base amount ```solidity event CollateralReservationDeleted( address indexed agentVault, address indexed minter, uint256 indexed collateralReservationId, uint256 reservedAmountUBA ); ``` ## Redemption Events All events related to the FAssets redemption process, including redemption requests, ticket management, successful redemptions, failures, defaults, and fee distributions. ### `RedemptionRequested` An event is emitted when the redeemer starts the redemption process. Parameters: - `agentVault`: Address of the agent vault. - `redeemer`: Address of the redeemer. - `requestId`: Unique identifier for the redemption request. - `paymentAddress`: Address to which the agent must transfer the redeemed amount on the underlying chain. - `valueUBA`: Amount of FAssets to redeem. - `feeUBA`: Fee for the redemption. - `firstUnderlyingBlock`: First underlying block to submit payment. - `lastUnderlyingBlock`: Last underlying block to submit payment. - `lastUnderlyingTimestamp`: Deadline on the underlying chain for submitting the redemption payment. - `paymentReference`: Reference for payment that will be used to track the redemption payment. - `executor`: Address of the executor that is allowed to execute the redemption default. - `executorFeeNatWei`: Fee for the executor in NAT wei. ```solidity event RedemptionRequested( address indexed agentVault, address indexed redeemer, uint256 indexed requestId, string paymentAddress, uint256 valueUBA, uint256 feeUBA, uint256 firstUnderlyingBlock, uint256 lastUnderlyingBlock, uint256 lastUnderlyingTimestamp, bytes32 paymentReference, address executor, uint256 executorFeeNatWei); ``` ### `RedemptionWithTagRequested` Emitted when the redeemer starts redemption with the tag and provides FAssets. The amount corresponding to `valueUBA` FAssets is burned. Several `RedemptionWithTagRequested` events can be emitted in a single redemption call (one per redeemed agent, with multiple tickets for the same agent combined). The agent's collateral remains locked at this stage. Parameters: - `agentVault`: Address of the agent vault. - `redeemer`: Address of the redeemer. - `requestId`: Unique identifier for the redemption request. - `paymentAddress`: Address on the underlying chain to which the agent must transfer the payment. - `valueUBA`: Amount redeemed in underlying base units. - `feeUBA`: Redemption fee in underlying base units. - `firstUnderlyingBlock`: First underlying block in the valid payment window. - `lastUnderlyingBlock`: Last underlying block in the valid payment window. - `lastUnderlyingTimestamp`: Last underlying timestamp in the valid payment window. - `paymentReference`: Payment reference that must be used in the underlying payment. - `executor`: Address allowed to execute redemption default (besides redeemer and agent). - `executorFeeNatWei`: Fee paid to the executor in NAT wei. - `destinationTag`: Destination tag required for the XRP payment. ```solidity event RedemptionWithTagRequested( address indexed agentVault, address indexed redeemer, uint256 indexed requestId, string paymentAddress, uint256 valueUBA, uint256 feeUBA, uint256 firstUnderlyingBlock, uint256 lastUnderlyingBlock, uint256 lastUnderlyingTimestamp, bytes32 paymentReference, address executor, uint256 executorFeeNatWei, uint256 destinationTag ); ``` ### `RedemptionTicketCreated` This event is emitted when a redemption ticket is created, when a minting transaction is executed. Parameters: ```solidity event RedemptionTicketCreated( address indexed agentVault, uint256 indexed redemptionTicketId, uint256 ticketValueUBA); ``` ### `RedemptionTicketUpdated` Event emitted when a redemption ticket value is changed (partially redeemed). Parameters: - `agentVault`: The address of the agent vault that will redeem the FAssets. - `redemptionTicketId`: The ID of the redemption ticket. - `ticketValueUBA`: The value of the redemption ticket in the underlying chain currency. ```solidity event RedemptionTicketUpdated( address indexed agentVault, uint256 indexed redemptionTicketId, uint256 ticketValueUBA); ``` ### `RedemptionPerformed` The event is emitted when the agent provides proof of redemption payment, and the agent's collateral is released. ```solidity event RedemptionPerformed( address indexed agentVault, address indexed redeemer, uint64 indexed requestId, bytes32 transactionHash, uint256 redemptionAmountUBA, int256 spentUnderlyingUBA); ``` ### `RedemptionRejected` Emitted when an agent rejects a redemption request due to an invalid redeemer address. Parameters: - `agentVault`: Address of the agent vault that rejected the redemption. - `redeemer`: Address of the user attempting to redeem (invalid address). - `requestId`: Unique identifier for the redemption request. - `redemptionAmountUBA`: Amount of FAssets that were requested for redemption. ```solidity event RedemptionRejected( address indexed agentVault, address indexed redeemer, uint256 indexed requestId, uint256 redemptionAmountUBA ); ``` ### `RedemptionRequestIncomplete` Emitted when a redemption request cannot be fully processed due to insufficient tickets or exceeding allowed redemption limits. Parameters: - `redeemer`: Address of the user who requested the redemption. - `remainingLots`: Number of FAsset lots that could not be redeemed and are returned to the redeemer. ```solidity event RedemptionRequestIncomplete( address indexed redeemer, uint256 remainingLots ); ``` ### `RedemptionAmountIncomplete` Emitted when only a partial redemption can occur, for instance, if there are insufficient redemption tickets or if fulfilling the entire request would exceed the allowed ticket limit. Parameters: - `redeemer`: Address of the user who requested redemption. - `remainingAmountUBA`: Amount (UBA) that could not be redeemed and is returned. ```solidity event RedemptionAmountIncomplete( address indexed redeemer, uint256 remainingAmountUBA ); ``` ### `RedemptionDefault` Emitted when a redemption payment times out and the redeemer is compensated with collateral instead. Parameters: - `agentVault`: Address of the agent vault that failed to complete the payment. - `redeemer`: Address of the user who requested the redemption. - `requestId`: Unique identifier for the redemption request. - `redemptionAmountUBA`: Amount of FAssets that were requested for redemption. - `redeemedVaultCollateralWei`: Amount of collateral paid from the agent's vault. - `redeemedPoolCollateralWei`: Amount of collateral paid from the collateral pool. ```solidity event RedemptionDefault( address indexed agentVault, address indexed redeemer, uint256 indexed requestId, uint256 redemptionAmountUBA, uint256 redeemedVaultCollateralWei, uint256 redeemedPoolCollateralWei ); ``` ### `RedemptionPaymentBlocked` Emitted when a redemption payment fails due to the redeemer's address being blocked or gas limit issues. Parameters: - `agentVault`: Address of the agent vault that attempted the payment. - `redeemer`: Address of the user who requested the redemption. - `requestId`: Unique identifier for the redemption request. - `transactionHash`: Hash of the failed transaction on the underlying chain. - `redemptionAmountUBA`: Amount of FAssets that were requested for redemption. - `spentUnderlyingUBA`: Amount of underlying currency spent in the failed attempt (negative value). ```solidity event RedemptionPaymentBlocked( address indexed agentVault, address indexed redeemer, uint256 indexed requestId, bytes32 transactionHash, uint256 redemptionAmountUBA, int256 spentUnderlyingUBA ); ``` ### `RedemptionPaymentFailed` Emitted when a redemption payment fails due to the agent's own error. Parameters: - `agentVault`: Address of the agent vault that failed the payment. - `redeemer`: Address of the user who requested the redemption. - `requestId`: Unique identifier for the redemption request. - `transactionHash`: Hash of the failed transaction on the underlying chain. - `spentUnderlyingUBA`: Amount of underlying currency spent in the failed attempt (negative value). - `failureReason`: Human-readable description of why the payment failed. ```solidity event RedemptionPaymentFailed( address indexed agentVault, address indexed redeemer, uint256 indexed requestId, bytes32 transactionHash, int256 spentUnderlyingUBA, string failureReason ); ``` ### `RedemptionPoolFeeMinted` Emitted when part of the redemption fee is re-minted as FAssets and paid to the agent's collateral pool. Parameters: - `agentVault`: Address of the agent vault that completed the redemption. - `requestId`: Unique identifier for the redemption request. - `poolFeeUBA`: Amount of FAssets re-minted and paid as a fee to the collateral pool. ```solidity event RedemptionPoolFeeMinted( address indexed agentVault, uint256 indexed requestId, uint256 poolFeeUBA ); ``` ### `RedeemedInCollateral` Emitted when FAssets are redeemed in collateral during a self-close exit process. Parameters: - `agentVault`: Address of the agent vault that processed the redemption. - `redeemer`: Address of the user who requested the redemption. - `redemptionAmountUBA`: Amount of FAssets that were redeemed. - `paidVaultCollateralWei`: Amount of collateral paid from the agent's vault. ```solidity event RedeemedInCollateral( address indexed agentVault, address indexed redeemer, uint256 redemptionAmountUBA, uint256 paidVaultCollateralWei ); ``` --- ## ICollateralPool Command line reference for interacting with FAssets `ICollateralPool` contract. Sourced from `ICollateralPool.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/ICollateralPool.sol). --- ## Overview This interface is used by pool participants and the agent vault owner to manage collateral, fees, and reward delegation. ## Functions ### `enter` Enters an agent's collateral pool by depositing NAT, where tokens are timelocked and may carry a [fee debt](/fassets/collateral#minting-fees-and-debt) if FAsset fees already exist in the pool. This debt must be cleared before tokens can be transferred. Parameters: - `_executor`: The account that is allowed to execute the entry (besides the user and agent). Returns: - `_receivedTokens`: The amount of pool tokens received. - `_timelockExpiresAt`: The timestamp when the timelock expires. ```solidity function enter() external payable returns (uint256 _receivedTokens, uint256 _timelockExpiresAt); ``` ### `exit` Exits the pool by redeeming pool tokens for a share of NAT and FAsset fees. Reverts if exiting would drop the collateral ratio below the exit [CR](/fassets/collateral#collateral-ratio). Parameters: - `_tokenShare`: Amount of pool tokens to redeem. Returns: - `_natShare`: Amount of NAT received. ```solidity function exit(uint256 _tokenShare) external returns (uint256 _natShare); ``` ### `selfCloseExit` Exits the pool by redeeming pool tokens and burning FAssets while preserving the pool's collateral ratio. FAssets are redeemed into collateral if their value does not exceed one [lot](/fassets/minting#lots). Parameters: - `_tokenShare`: The amount of pool tokens to be liquidated. - `_redeemToCollateral`: Specifies if redeemed FAssets should be exchanged to vault collateral by the agent. - `_redeemerUnderlyingAddress`: Redeemer's address on the underlying chain. - `_executor`: The account that is allowed to execute redemption. ```solidity function selfCloseExit( uint256 _tokenShare, bool _redeemToCollateral, string memory _redeemerUnderlyingAddress, address payable _executor ) external payable; ``` ### `withdrawFees` Collects FAsset fees by locking an appropriate ratio of transferable tokens. Parameters: - `_amount`: The amount of FAsset fees to withdraw (must be positive and smaller than or equal to the sender's FAsset fees). ```solidity function withdrawFees(uint256 _amount) external; ``` ### `exitTo` Exits the pool by redeeming pool tokens for a share of NAT and FAsset fees and transferring them to a recipient. Reverts if exiting would drop the collateral ratio below the exit [CR](/fassets/collateral#collateral-ratio). Parameters: - `_tokenShare`: The amount of pool tokens to be redeemed. - `_recipient`: Recipient address for NATs and FAsset fees. Returns: - `_natShare`: The amount of NAT received. ```solidity function exitTo(uint256 _tokenShare, address payable _recipient) external returns (uint256 _natShare); ``` ### `selfCloseExitTo` Exits the pool by redeeming pool tokens and burning FAssets, while preserving the pool's collateral ratio and allowing a recipient to be specified. FAssets are redeemed into collateral if their value does not exceed one [lot](/fassets/minting#lots). Parameters: - `_tokenShare`: The amount of pool tokens to be liquidated. - `_redeemToCollateral`: Specifies if redeemed FAssets should be exchanged to vault collateral by the agent. - `_recipient`: Recipient address for NATs and FAsset fees. - `_redeemerUnderlyingAddress`: Redeemer's address on the underlying chain. - `_executor`: The account that is allowed to execute redemption default. ```solidity function selfCloseExitTo( uint256 _tokenShare, bool _redeemToCollateral, address payable _recipient, string memory _redeemerUnderlyingAddress, address payable _executor ) external payable; ``` ### `withdrawFeesTo` Collect FAsset fees by locking an appropriate ratio of transferable tokens and transferring them to a recipient. Parameters: - `_amount`: The amount of FAsset fees to withdraw (must be positive and smaller than or equal to the sender's FAsset fees). - `_recipient`: The address to which FAsset fees will be transferred. ```solidity function withdrawFeesTo(uint256 _amount, address _recipient) external; ``` ### `payFAssetFeeDebt` Unlocks pool tokens by paying the FAsset fee debt. Parameters: - `_fassets`: The amount of debt FAsset fees to pay for. ```solidity function payFAssetFeeDebt(uint256 _fassets) external; ``` ### `claimAirdropDistribution` Claim airdrops earned by holding wrapped native tokens in the pool. Only the owner of the pool's corresponding agent vault may call this method. Parameters: - `_distribution`: The distribution contract to claim from. - `_month`: The month for which to claim the airdrop. Returns: - `_claimedAmount`: The amount of claimed airdrop. ```solidity function claimAirdropDistribution( IDistributionToDelegators _distribution, uint256 _month ) external returns(uint256 _claimedAmount); ``` ### `optOutOfAirdrop` Opt out of airdrops for wrapped native tokens in the pool. Only the owner of the pool's corresponding agent vault may call this method. Parameters: - `_distribution`: The distribution contract to opt out of. ```solidity function optOutOfAirdrop( IDistributionToDelegators _distribution ) external; ``` ### `delegate` Delegate WNat vote power for the wrapped native tokens held in this vault. Only the owner of the pool's corresponding agent vault may call this method. Parameters: - `_to`: The address to delegate to. - `_bips`: The delegation percentage in basis points (10000 = 100%). ```solidity function delegate(address _to, uint256 _bips) external; ``` ### `undelegateAll` Clear WNat delegation for the wrapped native tokens held in this vault. ```solidity function undelegateAll() external; ``` ### `claimDelegationRewards` Claim the rewards earned by delegating the voting power for the pool. Only the owner of the pool's corresponding agent vault may call this method. Parameters: - `_rewardManager`: The reward manager contract. - `_lastRewardEpoch`: The last reward epoch that was claimed. - `_proofs`: Array of reward claims with proofs. Returns: - `_claimedAmount`: The amount of claimed rewards. ```solidity function claimDelegationRewards( IRewardManager _rewardManager, uint24 _lastRewardEpoch, IRewardManager.RewardClaimWithProof[] calldata _proofs ) external returns(uint256 _claimedAmount); ``` ## View Functions ### `poolToken` Get the ERC20 pool token used by this collateral pool. Returns: - `ICollateralPoolToken`: The pool token contract. ```solidity function poolToken() external view returns (ICollateralPoolToken); ``` ### `agentVault` Get the vault of the agent that owns this collateral pool. Returns: - `address`: The agent vault address. ```solidity function agentVault() external view returns (address); ``` ### `exitCollateralRatioBIPS` Get the exit collateral ratio in BIPS. This is the collateral ratio below which exiting the pool is not allowed. Returns: - `uint32`: The exit collateral ratio in BIPS. ```solidity function exitCollateralRatioBIPS() external view returns (uint32); ``` ### `totalCollateral` Returns the total amount of collateral in the pool. This can differ from the WNat ERC-20 `balanceOf(poolAddress)` reading (see [`IWNat`](/network/solidity-reference/IWNat) for the underlying WNat interface) because the collateral must be tracked to prevent unexpected deposit type attacks on the pool. Returns: - `uint256`: Total collateral amount. ```solidity function totalCollateral() external view returns (uint256); ``` ### `fAssetFeesOf` Returns the FAsset fees belonging to a specific user. It is the amount of FAssets the user can withdraw by burning transferable pool tokens. Parameters: - `_account`: User address. Returns: - `uint256`: Amount of FAsset fees belonging to the user. ```solidity function fAssetFeesOf(address _account) external view returns (uint256); ``` ### `totalFAssetFees` Returns the total FAsset fees in the pool. It may differ from `FAsset.balanceOf(poolAddress)` because the collateral must be tracked to prevent unexpected deposit type attacks on the pool. Returns: - `uint256`: Total FAsset fees in the pool. ```solidity function totalFAssetFees() external view returns (uint256); ``` ### `fAssetFeeDebtOf` Returns the user's FAsset fee debt. This is the amount of FAssets the user has to pay to make all pool tokens transferable. The debt is created on entering the pool if the user does not provide the FAssets corresponding to the share of the FAsset fees already in the pool. Parameters: - `_account`: User address. Returns: - `int256`: User's FAsset fee debt (can be negative). ```solidity function fAssetFeeDebtOf(address _account) external view returns (int256); ``` ### `totalFAssetFeeDebt` Returns the total FAsset fee debt for all users. Returns: - `int256`: Total FAsset fee debt for all users (can be negative). ```solidity function totalFAssetFeeDebt() external view returns (int256); ``` ### `fAssetRequiredForSelfCloseExit` Get the amount of FAssets that need to be burned to perform [`selfCloseExit`](/fassets/reference/ICollateralPool#selfcloseexit) or [`selfCloseExitTo`](/fassets/reference/ICollateralPool#selfcloseexitto). Parameters: - `_tokenAmountWei`: The amount of pool tokens to exit. Returns: - `uint256`: Amount of FAssets required for self close exit. ```solidity function fAssetRequiredForSelfCloseExit(uint256 _tokenAmountWei) external view returns (uint256); ``` --- ## ICoreVaultClient `ICoreVaultClient` is the agent-facing surface of the FAssets **core vault** subsystem. Agents transfer backing into the core vault to free up capital, and request returns when they need to redeem. Direct minting (FAssets minted without an XRPL payment from the user) and direct redemptions also flow through this client. Sourced from `ICoreVaultClient.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/ICoreVaultClient.sol). ## Workflows The interface groups three lifecycle flows: 1. **Transfer to core vault** — `transferToCoreVault`, with `TransferToCoreVaultStarted` → `TransferToCoreVaultSuccessful` (or `TransferToCoreVaultDefaulted`) events. 2. **Return from core vault** — `requestReturnFromCoreVault` → `confirmReturnFromCoreVault`, with the corresponding `ReturnFromCoreVault*` events. 3. **Core-vault redemption** — `redeemFromCoreVault`, emitting `CoreVaultRedemptionRequested`. ## Events A non-exhaustive list of the events exposed by `ICoreVaultClient` (consult the source for the full list and field semantics): - `TransferToCoreVaultStarted(address agentVault, uint256 transferRedemptionRequestId, uint256 valueUBA)` - `TransferToCoreVaultDefaulted(address agentVault, uint256 transferRedemptionRequestId, uint256 remintedUBA)` - `TransferToCoreVaultSuccessful(address agentVault, uint256 transferRedemptionRequestId, uint256 valueUBA)` - `ReturnFromCoreVaultRequested(address agentVault, uint256 requestId, bytes32 paymentReference, uint256 valueUBA)` - `ReturnFromCoreVaultCancelled(address agentVault, uint256 requestId)` - `ReturnFromCoreVaultConfirmed(address agentVault, uint256 requestId, uint256 receivedUnderlyingUBA, uint256 remintedUBA)` - `CoreVaultRedemptionRequested(address redeemer, string paymentAddress, bytes32 paymentReference, uint256 valueUBA, uint256 feeUBA)` See the linked source for the full set of functions and accompanying NatSpec. --- ## ICoreVaultManager Command line reference for managing and interacting with FAssets `ICoreVaultSettings`. Sourced from `ICoreVaultManager.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/ICoreVaultManager.sol). To get the Core Vault Manager address, use the [getCoreVaultManager](/fassets/reference/IAssetManager#getcorevaultmanager) method on the [IAssetManager](/fassets/reference/IAssetManager) contract. ## Functions ### `getSettings` Returns the current operational settings of the Core Vault Manager. ```solidity function getSettings() external view returns ( uint128 _escrowEndTimeSeconds, uint128 _escrowAmount, uint128 _minimalAmount, uint128 _fee ); ``` #### Returns - `_escrowEndTimeSeconds`: The end time for escrow in seconds. - `_escrowAmount`: The amount of assets to be held in escrow. - `_minimalAmount`: The minimum amount that must remain in the vault after operations. - `_fee`: The fee charged for operations. ### `getAllowedDestinationAddresses` Returns the list of allowed destination addresses. ```solidity function getAllowedDestinationAddresses() external view returns (string[] memory); ``` ### `isDestinationAddressAllowed` Checks if the destination address is allowed. ```solidity function isDestinationAddressAllowed(string memory _address) external view returns (bool); ``` ### `coreVaultAddress` Returns the core vault address. ```solidity function coreVaultAddress() external view returns (string memory); ``` ### `coreVaultAddressHash` Returns the core vault address hash. ```solidity function coreVaultAddressHash() external view returns (bytes32); ``` ### `assetManager` Returns the corresponding asset manager. ```solidity function assetManager() external view returns (address); ``` ### `custodianAddress` Returns the custodian address. ```solidity function custodianAddress() external view returns (string memory); ``` ### `availableFunds` Returns the available funds. ```solidity function availableFunds() external view returns (uint128); ``` ### `escrowedFunds` Returns the escrowed funds. ```solidity function escrowedFunds() external view returns (uint128); ``` --- ## IFAsset `IFAsset` is the ERC-20 surface of an FAsset token (FXRP, FBTC, FDOGE, FLTC, ...) with three extra accessors that expose the underlying asset's identity and the `IAssetManager` bound to this FAsset. It extends OpenZeppelin's [`IERC20`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#IERC20) and [`IERC20Metadata`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#IERC20Metadata). Sourced from `IFAsset.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/IFAsset.sol). ## Functions ### assetName The name of the underlying asset (for example, `"XRP"` for FXRP). ```solidity function assetName() external view returns (string memory); ``` ### assetSymbol The symbol of the underlying asset (for example, `"XRP"`). ```solidity function assetSymbol() external view returns (string memory); ``` ### assetManager The address of the [`IAssetManager`](/fassets/reference/IAssetManager) instance that controls minting and redemption for this FAsset. FAssets and asset managers are in 1:1 correspondence. ```solidity function assetManager() external view returns (address); ``` --- ## IMintingTagManager Reference for managing and interacting with FAssets `IMintingTagManager`. Each minting tag is an [ERC721 token](https://ethereum.org/developers/docs/standards/tokens/erc-721/) that authorizes its owner to perform direct mintings. Tags are reserved by paying [a fee](/fassets/reference/IMintingTagManager#reservationfee) in native currency. Each tag has a configurable minting recipient (the address that receives minted FAssets) and an optional allowed executor (the only address permitted to execute direct mintings with that tag). To get the minting tag manager address, use the [getMintingTagManager](/fassets/reference/IAssetManager#getmintingtagmanager) function of the [IAssetManager](/fassets/reference/IAssetManager) contract. Sourced from `IMintingTagManager.sol` on [GitHub](https://github.com/flare-foundation/fassets/blob/main/contracts/userInterfaces/IMintingTagManager.sol). ## Functions ### `reserve` Reserve a new minting tag NFT by paying the reservation fee. The caller becomes the owner of this NFT (tag ID) and the initial minting recipient. ```solidity function reserve() external payable returns (uint256); ``` #### Returns - The newly reserved minting tag ID. ### `setMintingRecipient` Set the minting recipient for a tag. Only callable by the tag owner. The minting recipient is the address that receives minted FAssets when this tag is used. ```solidity function setMintingRecipient(uint256 _mintingTag, address _recipient) external; ``` #### Parameters - `_mintingTag`: The minting tag ID. - `_recipient`: The new minting recipient address (must not be the zero address). ### `setAllowedExecutor` Set the allowed executor for a tag. Only callable by the tag owner. The allowed executor is the only address that can execute direct mintings with this tag. Setting an allowed executor is optional; if not set, anyone can execute mintings with the tag. Changes to the allowed executor are subject to a cooldown delay before they become active. ```solidity function setAllowedExecutor(uint256 _mintingTag, address _executor) external; ``` #### Parameters - `_mintingTag`: The minting tag ID. - `_executor`: The new allowed executor address (must not be the zero address). ### `nextAvailableTag` Return the next minting tag ID that will be assigned on the next reservation. ```solidity function nextAvailableTag() external view returns (uint256); ``` ### `reservationFee` Return the fee (in native currency) required to reserve a new minting tag. ```solidity function reservationFee() external view returns (uint256); ``` ### `reservedTagsForOwner` Return all minting tag IDs owned by the given address. ```solidity function reservedTagsForOwner(address _owner) external view returns (uint256[] memory); ``` #### Parameters - `_owner`: The address to query. #### Returns - An array of minting tag IDs owned by `_owner`. ### `transfer` Transfer a minting tag to a new owner. Also updates the minting recipient to the new owner and resets the allowed executor. ```solidity function transfer(address _to, uint256 _mintingTag) external; ``` #### Parameters - `_to`: The address to transfer the tag to. - `_mintingTag`: The minting tag ID to transfer. ### `mintingRecipient` Return the minting recipient for a given tag. ```solidity function mintingRecipient(uint256 _mintingTag) external view returns (address); ``` #### Parameters - `_mintingTag`: The minting tag ID. #### Returns - The address that receives minted FAssets when this tag is used. ### `allowedExecutor` Return the currently active allowed executor for a given tag. If no executor is set or the pending change has not been activated yet, this returns the previous executor. ```solidity function allowedExecutor(uint256 _mintingTag) external view returns (address); ``` #### Parameters - `_mintingTag`: The minting tag ID. #### Returns - The address of the allowed executor, or `address(0)` if none is set. ### `pendingAllowedExecutorChange` Return information about a pending allowed executor change for a given tag. Executor changes are subject to a cooldown delay before they become active. During the cooldown, `allowedExecutor` continues to return the previous executor. Use this function to learn when a pending change takes effect so the current executor can avoid starting FDC-backed minting flows they will no longer be allowed to complete. ```solidity function pendingAllowedExecutorChange(uint256 _mintingTag) external view returns (bool _pending, address _newExecutor, uint256 _activeAfterTs); ``` #### Parameters - `_mintingTag`: The minting tag ID. #### Returns - `_pending`: `true` if there is a pending executor change that has not activated yet. - `_newExecutor`: The address of the pending new executor (`address(0)` if there is no pending change). - `_activeAfterTs`: The timestamp after which the new executor becomes active (`0` if there is no pending change). --- ## Get Firelight Vault Status This guide demonstrates how to retrieve information about a Firelight vault, including vault metrics, period configuration, user balances, and withdrawal information. The Firelight vault is an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) vault architecture compatible with [FAssets](/fassets/overview). ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) ## Firelight Vault Status Script The following script retrieves and displays information about the Firelight vault: {FirelightStatus} ## Script Breakdown The `main()` function executes the following steps: 1. **Get the account and vault instance:** Retrieves the signer account and connects to the Firelight vault contract. 2. **Get vault information:** Fetches vault data, including asset address, total assets, total supply, and period configuration. 3. **Get asset token information:** Retrieves the asset token's symbol and decimals for formatting. 4. **Log asset information:** Displays the asset address, symbol, and decimals. 5. **Log vault balances and exchange rate:** Shows total assets, total supply, and the current exchange rate. 6. **Log period configuration:** Displays period timing and configuration. Firelight vaults operate on [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic). 7. **Get and log user information:** Shows user balances and maximum limits for deposit, mint, withdraw, and redeem. 8. **Log user withdrawals:** Displays pending withdrawals for current and previous periods. ## Running the Script To run the Firelight vault status script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `FIRELIGHT_VAULT_ADDRESS` constant with the correct vault address for your network. 3. Run the script using Hardhat: ```bash yarn hardhat run scripts/firelight/status.ts --network coston2 ``` ## Output The script outputs the following information: ```bash === Asset === Asset address: 0x0b6A3645c240605887a5532109323A3E12273dc7 Asset symbol: FTestXRP Asset decimals: 6 === Vault Balances === Total assets (excl. pending withdrawals): 7990061 (7.990061 FTestXRP) Total supply (shares): 7990061 (7.990061 shares) Exchange rate: 1.000000 FTestXRP/share === Period Configuration === Period configurations count: 1 Current period: 108 Current period start: 1766966751 (2025-12-29T00:05:51.000Z) Current period end: 1767053151 (2025-12-30T00:05:51.000Z) Next period end: 1767139551 (2025-12-31T00:05:51.000Z) Current period config: { epoch: '1757635551', duration: '86400', startingPeriod: '0' } === User Info === Account: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 User balance (shares): 61 (0.000061 shares) User balance (assets): 61 (0.000061 FTestXRP) Max deposit: 115792089237316195423570985008687907853269984665640564039457584007913129639935 Max mint: 115792089237316195423570985008687907853269984665640564039457584007913129639935 Max withdraw: 61 Max redeem: 61 === User Withdrawals === ``` ## Summary In this guide, you learned how to retrieve status information from a Firelight vault, including vault metrics, period configuration, user balances, and withdrawal information. :::tip[What's next] To continue your Firelight development journey, you can: - Learn how to [deposit assets into a Firelight vault](/fxrp/firelight/deposit). - Learn how to [mint Firelight vault shares](/fxrp/firelight/mint) by specifying the number of shares. - Learn how to [withdraw assets from a Firelight vault](/fxrp/firelight/withdraw). - Learn how to [redeem assets from a Firelight vault](/fxrp/firelight/redeem). - Explore the [FAssets system overview](/fassets/overview) to understand the broader ecosystem. ::: --- ## Deposit Assets into Firelight Vault This guide demonstrates how to deposit assets into a Firelight vault. The Firelight vault implements the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard, which allows users to deposit assets (e.g., FXRP) and receive vault shares in return. Depositing assets is the process of transferring assets to the vault and receiving vault shares, which represent your proportional ownership of the vault's assets. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - Sufficient asset balance (e.g., FXRP) to deposit into the vault. ## Firelight Vault Deposit Script The following script demonstrates how to deposit assets into the Firelight vault: {FirelightDeposit} ## Script Breakdown The `main()` function executes the following steps: 1. **Get the account:** Retrieves the signer account from the Hardhat environment. 2. **Get the vault and asset token:** Connects to the vault contract and retrieves the underlying asset token. 3. **Get asset info:** Fetches the asset token's symbol and decimals. 4. **Calculate the deposit amount:** Converts the desired amount into the correct units based on decimals. 5. **Log deposit info:** Displays the deposit details, including sender, vault, and amount. 6. **Validate the deposit:** Checks if the amount exceeds the maximum allowed. 7. **Approve tokens for transfer:** Approves the vault to spend the deposit amount. 8. **Execute the deposit:** Calls `deposit()` to transfer assets and mint shares. ## Running the Script To run the Firelight vault deposit script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `FIRELIGHT_VAULT_ADDRESS` constant with the correct vault address for your network. 3. Adjust the `tokensToDeposit` constant to the desired number of tokens. 4. Ensure your account has sufficient asset balance (e.g., FXRP) to cover the deposit. 5. Run the script using Hardhat: ```bash yarn hardhat run scripts/firelight/deposit.ts --network coston2 ``` ## Output The script outputs the following information: ```bash === Deposit (ERC-4626) === Sender: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Vault: 0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B Asset: 0x0b6A3645c240605887a5532109323A3E12273dc7 (FTestXRP, decimals=6) Deposit amount: 1000000 (= 1 FTestXRP) Max deposit: 115792089237316195423570985008687907853269984665640564039457584007913129639935 Approve tx: 0x87a36c1009575719fd3adb9a1bb2e3062a601bf910fc7fac3248da71891c39a4 Deposit tx: 0x446b7a171859d676677fc870cff81c7e8c0d618fc3588e60665792da86b94c50 ``` ## Difference Between Deposit and Mint The Firelight vault provides two ways to add assets: - **`deposit`**: You specify the amount of assets to deposit, and the vault calculates how many shares you'll receive based on the current exchange rate. - **`mint`**: You specify the number of shares you want, and the vault calculates how many assets you need to deposit. Both functions follow the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard and result in the same outcome: you deposit assets and receive vault shares. ## Summary In this guide, you learned how to deposit assets into a Firelight vault by specifying the amount of assets to deposit. :::tip[What's next] To continue your Firelight development journey, you can: - Learn how to [get Firelight vault status](/fxrp/firelight/status) to monitor your position. - Learn how to [mint Firelight vault shares](/fxrp/firelight/mint) by specifying the number of shares. - Learn how to [withdraw assets from a Firelight vault](/fxrp/firelight/withdraw). - Learn how to [redeem assets from a Firelight vault](/fxrp/firelight/redeem). - Explore the [FAssets system overview](/fassets/overview) to understand the broader ecosystem. ::: --- ## Mint Firelight Vault Shares This guide demonstrates how to mint vault shares in a Firelight vault by depositing assets. The Firelight vault implements the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard, which allows users to deposit assets (e.g., FXRP) and receive vault shares in return. Minting shares is the process of depositing assets into the vault and receiving vault shares, which represent your proportional ownership of the vault's assets. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - Sufficient asset balance (e.g., FXRP) to deposit into the vault. ## Firelight Vault Mint Script The following script demonstrates how to mint vault shares by depositing assets into the Firelight vault: {FirelightMint} ## Script Breakdown The `main()` function executes the following steps: 1. **Get the account:** Retrieves the signer account from the Hardhat environment. 2. **Get the vault and asset token:** Connects to the vault contract and retrieves the underlying asset token. 3. **Get asset info:** Fetches the asset token's symbol and decimals. 4. **Calculate the shares amount to mint:** Converts the desired shares into the correct units based on decimals. 5. **Log mint info:** Displays the mint details, including sender, vault, and shares. 6. **Validate the mint:** Checks if the amount exceeds the maximum allowed. 7. **Calculate assets needed:** Uses `previewMint()` to determine the required assets. 8. **Approve tokens for transfer:** Approves the vault to spend the required assets. 9. **Execute the mint:** Calls `mint()` to transfer assets and mint shares. ## Running the Script To run the Firelight vault mint script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `FIRELIGHT_VAULT_ADDRESS` constant with the correct vault address for your network. 3. Adjust the `sharesToMint` constant to the desired number of shares. 4. Ensure your account has sufficient asset balance (e.g., FXRP) to cover the minting cost. 5. Run the script using Hardhat: ```bash yarn hardhat run scripts/firelight/mint.ts --network coston2 ``` ## Output The script outputs the following information: ```bash === Mint vault shares (ERC-4626) === Sender: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Vault: 0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B Asset: 0x0b6A3645c240605887a5532109323A3E12273dc7 (FTestXRP, decimals=6) Shares to mint: 1000000 (= 1 share) Max mint: 115792089237316195423570985008687907853269984665640564039457584007913129639935 Assets needed (from previewMint): 1000000 Approve tx: 0xfe5683b82a4997df7d7b7c22c8c4dc416cdec8d1380dceeb996279c64c525460 Mint tx: 0x14d1c7ffe4f3b6a9fa04315eb8592ff9c64f2ae80c7e6e3a6e1b9cf9478106c3 ``` ## Difference Between Mint and Deposit The Firelight vault provides two ways to add assets: - **`mint`**: You specify the number of shares you want, and the vault calculates how many assets you need to deposit. - **`deposit`**: You specify the amount of assets to deposit, and the vault calculates how many shares you'll receive based on the current exchange rate. Both functions follow the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard and result in the same outcome: you deposit assets and receive vault shares. ## Summary In this guide, you learned how to mint vault shares in a Firelight vault by specifying the number of shares you want. :::tip[What's next] To continue your Firelight development journey, you can: - Learn how to [get Firelight vault status](/fxrp/firelight/status) to monitor your position. - Learn how to [deposit assets into a Firelight vault](/fxrp/firelight/deposit) by specifying the amount of assets to deposit. - Learn how to [withdraw assets from a Firelight vault](/fxrp/firelight/withdraw). - Learn how to [redeem assets from a Firelight vault](/fxrp/firelight/redeem). - Explore the [FAssets system overview](/fassets/overview) to understand the broader ecosystem. ::: --- ## Withdraw Assets from Firelight Vault This guide demonstrates how to create a withdrawal request from a Firelight vault. The Firelight vault implements the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard and uses a [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic) for withdrawals. Withdrawing assets creates a withdrawal request that is processed after the current period ends. The assets are not immediately transferred; they must be claimed once the period has ended. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - Vault shares in the Firelight vault to withdraw. ## Firelight Vault Withdraw Script The following script demonstrates how to create a withdrawal request from the Firelight vault: {FirelightWithdraw} ## Script Breakdown The `main()` function executes the following steps: 1. **Get the account:** Retrieves the signer account from the Hardhat environment. 2. **Get the vault and asset token:** Connects to the vault contract and retrieves the underlying asset token. 3. **Get asset info:** Fetches the asset token's symbol and decimals. 4. **Calculate the withdrawal amount:** Converts the desired amount into the correct units based on decimals. 5. **Log withdraw info:** Displays the withdrawal details, including sender, vault, and amount. 6. **Validate the withdrawal:** Checks if the amount exceeds the maximum allowed. 7. **Check user balance and shares needed:** Verifies the user has sufficient shares for the withdrawal. 8. **Execute the withdrawal:** Calls `withdraw()` to create a withdrawal request for the current period. ## Understanding Withdrawal Process The Firelight vault uses a period-based withdrawal system: 1. **Withdrawal Request**: When you call `withdraw`, it creates a withdrawal request associated with the current period. 2. **Period End**: The withdrawal is processed after the current period ends. 3. **Claim Withdrawal**: Once the period has ended, you must claim the withdrawal to receive your assets. This delayed withdrawal mechanism is part of the vault's [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic) and helps manage liquidity and ensure fair distribution of rewards. ## Running the Script To run the Firelight vault withdraw script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `FIRELIGHT_VAULT_ADDRESS` constant with the correct vault address for your network. 3. Adjust the `tokensToWithdraw` constant to the desired number of tokens. 4. Ensure your account has sufficient vault shares to cover the withdrawal. 5. Run the script using Hardhat: ```bash yarn hardhat run scripts/firelight/withdraw.ts --network coston2 ``` ## Output The script outputs the following information: ```bash === Withdraw (ERC-4626) === Sender: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Vault: 0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B Asset: 0x0b6A3645c240605887a5532109323A3E12273dc7 (FTestXRP, decimals=6) Withdraw amount: 1000000 (= 1 FTestXRP) Max withdraw: 2000061 User balance (shares): 2000061 (= 2.000061 shares) Withdraw tx: 0x23d8f34b582ef9935d3e3686a15e7fff34417ac01f68f7f928b14e2b4ef10ba9 ``` ## Difference Between Withdraw and Redeem The Firelight vault provides two ways to remove assets: - **`withdraw`**: You specify the amount of assets to withdraw, and the vault calculates how many shares need to be burned based on the current exchange rate. - **`redeem`**: You specify the number of shares to redeem, and the vault calculates how many assets you'll receive. Both functions follow the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard and create withdrawal requests that must be claimed after the period ends. ## Summary In this guide, you learned how to create a withdrawal request from a Firelight vault by specifying the amount of assets to withdraw. Remember that withdrawals are delayed and must be claimed after the current period ends. :::tip[What's next] To continue your Firelight development journey, you can: - Learn how to [claim withdrawals from a Firelight vault](/fxrp/firelight/claim) after the period ends. - Learn how to [get Firelight vault status](/fxrp/firelight/status) to monitor your withdrawal requests. - Learn how to [redeem assets from a Firelight vault](/fxrp/firelight/redeem) by specifying the number of shares. - Learn how to [deposit assets into a Firelight vault](/fxrp/firelight/deposit). - Learn how to [mint Firelight vault shares](/fxrp/firelight/mint) by specifying the number of shares. - Explore the [FAssets system overview](/fassets/overview) to understand the broader ecosystem. ::: --- ## Redeem Assets from Firelight Vault This guide demonstrates how to create a redemption request from a Firelight vault. The Firelight vault implements the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard and uses a [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic) for redemptions. Redeeming shares burns vault shares and allows assets to be withdrawn. Redemptions create a withdrawal request that is processed after the current period ends. The assets are not immediately transferred; they must be claimed once the period has ended. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - Vault shares in the Firelight vault to redeem. ## Firelight Vault Redeem Script The following script demonstrates how to create a redemption request from the Firelight vault: {FirelightRedeem} ## Script Breakdown The `main()` function executes the following steps: 1. **Get the account:** Retrieves the signer account from the Hardhat environment. 2. **Get the vault and asset token:** Connects to the vault contract and retrieves the underlying asset token. 3. **Get asset info:** Fetches the asset token's symbol and decimals. 4. **Calculate the shares amount to redeem:** Converts the desired shares into the correct units based on decimals. 5. **Log redeem info:** Displays the redemption details, including sender, vault, and shares. 6. **Validate the redeem:** Checks if the amount exceeds the maximum allowed. 7. **Check user balance:** Verifies the user has sufficient shares. 8. **Execute the redemption:** Calls `redeem()` to create a redemption request for the current period. ## Understanding Redemption Process The Firelight vault uses a period-based redemption system: 1. **Redemption Request**: When you call `redeem`, it creates a redemption request associated with the current period. 2. **Period End**: The redemption is processed after the current period ends. 3. **Claim Redemption**: Once the period has ended, you must claim the redemption to receive your assets. This delayed redemption mechanism is part of the vault's [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic) and helps manage liquidity and ensure fair distribution of rewards. ## Running the Script To run the Firelight vault redeem script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `FIRELIGHT_VAULT_ADDRESS` constant with the correct vault address for your network. 3. Adjust the `sharesToRedeem` constant to the desired number of shares. 4. Ensure your account has sufficient vault shares to cover the redemption. 5. Run the script using Hardhat: ```bash yarn hardhat run scripts/firelight/redeem.ts --network coston2 ``` ## Output The script outputs the following information: ```bash === Redeem (ERC-4626) === Sender: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Vault: 0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B Asset: 0x0b6A3645c240605887a5532109323A3E12273dc7 (FTestXRP, decimals=6) Shares to redeem: 1000000 (= 1 share) Max redeem: 1000061 User balance (shares): 1000061 (= 1.000061 shares) Redeem tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 ``` ## Difference Between Redeem and Withdraw The Firelight vault provides two ways to remove assets: - **`redeem`**: You specify the number of shares to redeem, and the vault calculates how many assets you'll receive. - **`withdraw`**: You specify the amount of assets to withdraw, and the vault calculates how many shares need to be burned based on the current exchange rate. Both functions follow the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard and create withdrawal requests that must be claimed after the period ends. ## Summary In this guide, you learned how to create a redemption request from a Firelight vault by specifying the number of shares to redeem. Remember that redemptions are delayed and must be claimed after the current period ends. :::tip[What's next] To continue your Firelight development journey, you can: - Learn how to [claim withdrawals from a Firelight vault](/fxrp/firelight/claim) after the period ends. - Learn how to [get Firelight vault status](/fxrp/firelight/status) to monitor your redemption requests. - Learn how to [withdraw assets from a Firelight vault](/fxrp/firelight/withdraw) by specifying the amount of assets. - Learn how to [deposit assets into a Firelight vault](/fxrp/firelight/deposit). - Learn how to [mint Firelight vault shares](/fxrp/firelight/mint) by specifying the number of shares. - Explore the [FAssets system overview](/fassets/overview) to understand the broader ecosystem. ::: --- ## Claim Withdrawals from Firelight Vault This guide demonstrates how to claim pending withdrawals from a Firelight vault. The Firelight vault implements the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard and uses a [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic) for withdrawals. Claiming is the final step in the withdrawal process. After you have created a withdrawal request using [`withdraw`](/fxrp/firelight/withdraw) or [`redeem`](/fxrp/firelight/redeem), you must wait for the period to end and then claim your assets. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview). - A pending withdrawal request from a previous [period](https://docs.firelight.finance/technical-documents#period-based-logic). ## Understanding the Claim Process The Firelight vault uses a period-based withdrawal system with three steps: 1. **Withdrawal Request:** When you call [`withdraw`](/fxrp/firelight/withdraw) or [`redeem`](/fxrp/firelight/redeem), it creates a withdrawal request associated with the current period. Your shares are burned at this point. 2. **Period End:** The withdrawal is processed after the current period ends. 3. **Claim Withdrawal:** Once the period has ended, you must call [`claimWithdraw`](https://docs.firelight.finance/technical-documents#claimwithdraw) function to transfer the assets to your wallet. ## Firelight Vault Claim Script The following script demonstrates how to claim pending withdrawals from the Firelight vault: {FirelightClaim} ## Script Breakdown The `main()` function executes the following steps: 1. **Get the account:** Retrieves the signer account from the Hardhat environment. 2. **Get the vault and asset token:** Connects to the vault contract and retrieves the underlying asset token. 3. **Get asset info:** Fetches the asset token's symbol and decimals. 4. **Log claim info:** Displays the claim details, including sender and vault addresses. 5. **Get current period info:** Retrieves the current period number and when it ends. 6. **Find claimable periods:** Scans all past periods to find any with claimable withdrawals. 7. **Log claimable periods:** Displays all periods with pending withdrawals and the total claimable amount. 8. **Execute claims:** Claims withdrawals for all claimable periods (or a specific period if configured). Only withdrawals from completed periods can be claimed. The script automatically detects all claimable periods and processes them. ## Running the Script To run the Firelight vault claim script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `FIRELIGHT_VAULT_ADDRESS` constant with the correct vault address for your network. 3. Optionally set `periodToClaim` to claim a specific period (0 means auto-detect all claimable periods). 4. Run the script using Hardhat: ```bash yarn hardhat run scripts/firelight/claim.ts --network coston2 ``` ## Output The script outputs the following information: ```bash === Claim Withdrawals (ERC-4626) === Sender: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Vault: 0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B Asset: 0x0b6A3645c240605887a5532109323A3E12273dc7 (FTestXRP, decimals=6) === Period Info === Current period: 5 Current period ends: 2025-01-15 12:00:00 UTC === Claimable Withdrawals === Period 3: 1000000 (1.000000 FTestXRP) Period 4: 500000 (0.500000 FTestXRP) Total claimable: 1500000 (1.500000 FTestXRP) Claim tx (period 3): 0x23d8f34b582ef9935d3e3686a15e7fff34417ac01f68f7f928b14e2b4ef10ba9 Claim tx (period 4): 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 ``` ## Summary In this guide, you learned how to claim pending withdrawals from a Firelight vault. Remember that claims can only be made after the withdrawal period has ended, and the script can automatically detect and claim all pending withdrawals across multiple periods. :::tip[What's next] To continue your Firelight development journey, you can: - Learn how to [get Firelight vault status](/fxrp/firelight/status) to monitor your withdrawal requests. - Learn how to [withdraw assets from a Firelight vault](/fxrp/firelight/withdraw) by specifying the amount of assets. - Learn how to [redeem assets from a Firelight vault](/fxrp/firelight/redeem) by specifying the number of shares. - Learn how to [deposit assets into a Firelight vault](/fxrp/firelight/deposit). - Explore the [FAssets system overview](/fassets/overview) to understand the broader ecosystem. ::: --- ## Firelight Vaults [Firelight](https://firelight.finance) is a yield-generating protocol built on top of FXRP. It provides [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) compliant vault that allows users to deposit FXRP and earn yield through the [FAssets](/fassets/overview) system. Firelight vaults operate on a [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic), where deposits and withdrawals are processed at specific intervals. Users receive vault shares representing their proportional ownership of the vault's assets. ## How Firelight Vaults Work This diagram shows the flow of operations for a user interacting with a Firelight vault. ```mermaid flowchart TB UserFXRP[("User's FXRP")] Deposit[deposit] Mint[mint] Shares[("Vault shares")] UserFXRP --> Deposit --> Shares UserFXRP --> Mint --> Shares Shares --> Withdraw[withdraw] Shares --> Redeem[redeem] Withdraw --> Request["Withdrawal request(current period)"] Redeem --> Request Request --> Wait["Wait for period end"] Wait --> Claim[claimWithdraw] Claim --> FXRP[("FXRP to user")] ``` The flow of operations for a user interacting with a Firelight vault is as follows: - **Deposit / Mint:** Two ways to get vault shares. Call `deposit(assets)` to deposit FXRP and receive the corresponding shares, or `mint(shares)` to specify the shares you want and pay the required FXRP. - **Withdraw / Redeem:** Both create a withdrawal request for the current period. Call `withdraw(assets)` to request a given amount of assets, or `redeem(shares)` to burn shares and request the equivalent assets. Shares are burned when you redeem. - **Claim:** After the period ends, call `claimWithdraw()` to receive your FXRP. Use the [Get Vault Status](/fxrp/firelight/status) and [Claim Withdrawals](/fxrp/firelight/claim) guides to find and claim completed periods. :::tip[What's next] - Learn more about [FAssets](/fassets/overview) and how the system works. - Explore how to [mint FXRP](/fassets/developer-guides/fassets-mint) from XRP. - Discover how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) back to XRP. ::: --- ## Auto Minting and Bridging FXRP ## Overview In this guide, you will learn how to mint FXRP and bridge it cross-chain from Flare Testnet Coston2 to Sepolia using [Flare Smart Accounts](/smart-accounts/overview) and [LayerZero's OFT](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) (Omnichain Fungible Token) protocol. This guide demonstrates an end-to-end workflow that allows XRPL users to: 1. **Mint FXRP** - Convert native XRP from the XRP Ledger into FXRP tokens on Flare, controlled entirely via XRPL transactions. 2. **Bridge FXRP cross-chain** - Transfer the minted FXRP to another EVM chain (Sepolia) using LayerZero, triggered by a single XRPL payment. **Key technologies:** - [Flare Smart Accounts](/smart-accounts/overview) - Account abstraction enabling XRPL users to execute actions on Flare without holding FLR tokens. - **Custom Instructions** - Register arbitrary contract calls that can be triggered via XRPL payments. - [FAsset System](/fassets/overview) for tokenizing non-smart contract assets (XRP to FXRP). - [LayerZero OFT](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) for cross-chain token transfers. - [@flarenetwork/smart-accounts-encoder](https://www.npmjs.com/package/@flarenetwork/smart-accounts-encoder) library for encoding FAsset instructions. Clone the [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) to follow along. ## How Smart Accounts Enable This Workflow [Flare Smart Accounts](/smart-accounts/overview) allow XRPL users to perform actions on the Flare chain without owning any FLR tokens. Each XRPL address is assigned a unique **personal account** (smart contract wallet) on Flare, which only that XRPL address can control through `Payment` transactions on the XRP Ledger. This script leverages two types of Smart Account instructions: ### 1. FAsset Collateral Reservation Instruction The [`FXRPCollateralReservationInstruction`](/smart-accounts/fasset-instructions#00-collateral-reservation) is a predefined instruction type that initiates the FAsset minting process. When sent via an XRPL payment memo: 1. The operator monitors the XRPL payment and relays it to Flare. 2. The `MasterAccountController` contract reserves collateral from the selected agent. 3. After the user sends XRP to the agent's underlying address, FXRP is minted to their personal account. ### 2. Custom Instruction for Atomic Bridging Custom instructions allow registering arbitrary contract calls that execute atomically. This script registers an **atomic batch** containing: 1. **Approve**: Grant the OFT Adapter permission to spend FXRP tokens. 2. **Send**: Execute the LayerZero cross-chain transfer. Both actions execute in a single transaction when triggered by the XRPL payment, ensuring the bridge cannot fail due to missing approval. ## Flow Diagram ``` XRPL USER WORKFLOW ------------------------------------------------------------------------ | ----------------------------|---------------------------- | | | v | | +-------------------+ | | | 1. Register | | | | Custom Bridge | | | | Instruction | | | | (Flare EOA) | | | +--------+----------+ | | | | | | Returns instruction hash | | v | | +-------------------+ | | | 2. Check Smart | | | | Account | | | | Balance | | | +--------+----------+ | | | | | | Needs FXRP? | | v v | +-------------------+ +-------------------+ | | 3a. Mint FXRP | | 3b. Skip Mint | | | (If needed) | | (Has balance) | | +--------+----------+ +--------+----------+ | | | | +-----------+-------------+ | | | v | +-------------------+ | | 4. Execute |<---------------------------------+ | Bridge | | (XRPL Payment) | +--------+----------+ | v CROSS-CHAIN FLOW ------------------------------------------------------------------------ +-------------------+ +-------------------+ +-------------------+ | XRP Ledger | | Flare Coston2 | | Sepolia | | | | | | | | XRPL Payment |--------->| MasterAccount | | | | with Memo | FDC | Controller | | | | | Proof | | | | | | | | v | | | | | | Personal Account | | | | | | | | | | | | | v | | | | | | Atomic Batch: | | | | | | +-------------+ | | | | | | | 1. Approve | | | | | | | | 2. LZ Send |--|--------->| FXRP OFT | | | | +-------------+ |LayerZero | Received | | | | | | | +-------------------+ +-------------------+ +-------------------+ ``` ## Prerequisites - **XRPL Testnet Account**: An XRP Ledger testnet wallet with XRP for payments. Get testnet XRP from the [XRP Testnet Faucet](https://xrpl.org/resources/dev-tools/xrp-faucets). - **Flare Testnet Account**: An EVM wallet with C2FLR for gas fees. Get C2FLR from the [Flare Testnet Faucet](https://faucet.flare.network/coston2). - **Environment Setup**: Private keys configured in Hardhat. ## Configuration Edit the `CONFIG` object in the script to customize the bridge parameters: ```typescript const CONFIG = { MASTER_ACCOUNT_CONTROLLER: "0xa7bc2aC84DB618fde9fa4892D1166fFf75D36FA6", COSTON2_OFT_ADAPTER: "0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639", XRPL_RPC: "wss://s.altnet.rippletest.net:51233", SEPOLIA_EID: EndpointId.SEPOLIA_V2_TESTNET, EXECUTOR_GAS: 400_000, BRIDGE_LOTS: 1, // Number of lots to bridge AUTO_MINT_IF_NEEDED: true, // Automatically mint if insufficient balance MINT_LOTS: 1, // Number of lots to mint if needed }; ``` | Parameter | Description | | --------------------------- | ----------------------------------------------------------------------------------- | | `MASTER_ACCOUNT_CONTROLLER` | Address of the MasterAccountController contract on Coston2 | | `COSTON2_OFT_ADAPTER` | Address of the FXRP OFT Adapter for LayerZero bridging | | `XRPL_RPC` | WebSocket URL for XRPL Testnet | | `SEPOLIA_EID` | LayerZero Endpoint ID for the destination chain | | `EXECUTOR_GAS` | Gas limit for the LayerZero executor on the destination chain | | `BRIDGE_LOTS` | Number of FXRP lots to bridge (1 lot = 10 FXRP) | | `AUTO_MINT_IF_NEEDED` | Whether to automatically mint FXRP if the personal account has insufficient balance | | `MINT_LOTS` | Number of lots to mint if auto-minting is triggered | ## How to Run 1. **Install Dependencies**: ```bash yarn install ``` 2. **Configure Environment**: ```bash # .env file COSTON2_RPC_URL=https://coston2-api.flare.network/ext/C/rpc DEPLOYER_PRIVATE_KEY=your_flare_private_key_here XRPL_SECRET=your_xrpl_wallet_secret_here ``` 3. **Run the Script**: ```bash yarn hardhat run scripts/smartAccounts/bridgeViaSmartAccount.ts --network coston2 ``` ## Script Walkthrough ### Step 1: Register the Bridge Instruction The script first registers a custom instruction with the `MasterAccountController`. This instruction bundles two contract calls into an atomic batch: ```typescript // 1. Prepare APPROVE Call const instructionApprove: CustomInstruction = { targetContract: fxrpAddress, value: 0n, data: approveCallData, // ERC20 approve(spender, amount) }; // 2. Prepare SEND Call const instructionBridge: CustomInstruction = { targetContract: CONFIG.COSTON2_OFT_ADAPTER, value: nativeFee, // LayerZero fee in native tokens data: sendCallData, // OFT send() with LayerZero options }; // 3. Register the atomic batch const atomicInstruction = [instructionApprove, instructionBridge]; await masterController.methods .registerCustomInstruction(atomicInstruction) .send({ from: accounts[0] }); ``` The registration returns an **instruction hash** that will be used as the XRPL payment memo to trigger execution. The memo format for custom instructions is: - First byte: `99` (custom instruction identifier) - Remaining 31 bytes: instruction hash (padded) ### Step 2: Check Smart Account Balance Before bridging, the script checks if the user's personal account has sufficient: 1. **FXRP balance** - Enough tokens to bridge 2. **Native balance (C2FLR)** - Enough gas to pay for the LayerZero fee ```typescript const personalAccountAddr = await masterController.methods .getPersonalAccount(xrplAddress) .call(); const fxrpBalance = await ftestxrp.balanceOf(personalAccountAddr); const nativeBalance = await web3.eth.getBalance(personalAccountAddr); ``` If the personal account doesn't exist yet, it will be created automatically when the first instruction is executed. ### Step 3: Fund Gas (If Needed) If the personal account lacks sufficient native tokens for the LayerZero fee, the script funds it from the Flare EOA: ```typescript if (status.needsGas && status.hasAccount) { await web3.eth.sendTransaction({ from: accounts[0], to: status.personalAccountAddr, value: (requiredGas - status.currentNative + BigInt(1e17)).toString(), }); } ``` ### Step 4: Mint FXRP (If Needed) If the personal account has insufficient FXRP, the script initiates the [FAsset minting process](/fassets/minting) using the Smart Accounts system: ```typescript // Encode the collateral reservation instruction const reservationInstruction = new FXRPCollateralReservationInstruction({ walletId: 0, value: lots, // Number of lots to mint agentVaultId: agentIndex, // Selected agent's index }); // Send XRPL payment with the instruction memo const instructionMemo = reservationInstruction.encode().slice(2); await sendXrplMemoPayment(xrplWallet, operatorAddress, "1", instructionMemo); ``` The minting process involves: 1. **Reservation Trigger**: Send an XRPL payment to the operator with the encoded instruction. 2. **Wait for Reservation**: The operator processes the payment and reserves collateral on Flare. 3. **Send Underlying Collateral**: Send XRP to the agent's underlying address with the payment reference. 4. **Mint Execution**: The operator executes the mint, depositing FXRP to the personal account. ### Step 5: Execute the Bridge Finally, trigger the bridge by sending an XRPL payment with the custom instruction memo: ```typescript await sendXrplMemoPayment(xrplWallet, operatorAddress, "0.1", bridgeMemo); ``` When the operator relays this payment to Flare: 1. The `MasterAccountController` looks up the registered instruction by its hash. 2. The personal account executes the atomic batch: - Approves the OFT Adapter to spend FXRP - Calls the OFT Adapter's `send()` function 3. LayerZero delivers the tokens to Sepolia. ## Expected Output ``` Flare EOA: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e XRPL Wallet: rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh Bridging 1 lot(s) = 10.0 FXRP === Step 1: Registering Atomic Bridge Instruction === LayerZero Fee: 0.001234 C2FLR required in personal account Submitting registration tx... Instruction Registered. Final XRPL Memo: 99000000...abc123 === Checking Smart Account Balance === Personal Account: 0x123... FXRP Balance: 15.0 C2FLR Balance: 0.5 Sufficient FXRP balance found. Skipping mint. === Bridging to Sepolia via Custom Instruction === Sending Bridge Trigger on XRPL... Sending 0.1 XRP to rOperator... with Memo 99000000...abc123 Tx Hash: ABC123... Bridge Request Sent! (Asynchronous execution on Flare will follow) ```
View `bridgeViaSmartAccount.ts` source code {bridgeViaSmartAccount}
## Code Breakdown ### Key Functions #### `getAssetManagerInfo(lots)` Retrieves the FXRP token address and calculates the exact amount to bridge based on the lot size: ```typescript const assetManager = await getAssetManagerFXRP(); const fxrpAddress = await assetManager.fAsset(); const lotSize = BigInt(await assetManager.lotSize()); const amountToBridge = lotSize * BigInt(lots); ``` #### `registerBridgeInstruction(recipientAddress, amountToBridge, fxrpAddress)` Creates and registers the atomic bridge instruction: 1. Encodes the ERC20 `approve()` call for the OFT Adapter. 2. Builds LayerZero send parameters with the destination chain and recipient. 3. Quotes the LayerZero fee using `oftAdapter.quoteSend()`. 4. Encodes the OFT `send()` call with the fee. 5. Registers both calls as a single atomic instruction. #### `sendXrplMemoPayment(xrplWallet, destination, amountXrp, memoHex)` Sends an XRP Ledger payment with an encoded memo: ```typescript const payment: Payment = { TransactionType: "Payment", Account: xrplWallet.address, Destination: destination, Amount: xrpToDrops(amountXrp), Memos: [{ Memo: { MemoData: memoHex.toUpperCase() } }], }; ``` #### `mintFXRP(xrplWallet, lots)` Executes the full FAsset minting flow: 1. Selects an available agent with sufficient free collateral. 2. Encodes and sends the collateral reservation instruction. 3. Waits for the `CollateralReserved` event. 4. Sends XRP collateral to the agent's underlying address. 5. Waits for the mint to complete. ## Understanding the Instruction Encoding ### FAsset Instructions The `@flarenetwork/smart-accounts-encoder` library provides typed instruction builders. The collateral reservation instruction encodes to a 32-byte value: | Bytes | Content | | ----- | -------------------------------------------------------------------- | | 0 | Instruction ID (`00` for FXRP type, `00` for collateral reservation) | | 1 | Wallet ID (typically `0`) | | 2-11 | Value (number of lots) | | 12-13 | Agent Vault ID | | 14-31 | Reserved | ### Custom Instructions Custom instructions are identified by the first byte being `99`. The remaining 31 bytes contain the keccak256 hash of the encoded instruction array (right-shifted by 8 bits): ```typescript uint256(keccak256(abi.encode(_customInstruction))) >> 8; ``` ## FAQ **Q: What's the minimum amount I can bridge?** A: Minimum is 1 [lot](/fassets/minting#lots) (10 FXRP for XRP). **Q: How long does the minting process take?** A: Minting typically takes 1-2 minutes for the reservation, plus time for the underlying XRP payment to be confirmed and processed by the operator. **Q: What if the bridge execution fails?** A: If the atomic instruction fails (e.g., insufficient balance), the entire transaction reverts. The FXRP remains in the personal account and can be retrieved or retried. **Q: Can I bridge to chains other than Sepolia?** A: Yes, update the `SEPOLIA_EID` configuration to any LayerZero-supported destination. Use the [getOftPeers script](/fxrp/oft/fxrp-autoredeem#discovering-available-bridge-routes) to discover available routes. **Q: Do I need FLR tokens to use this?** A: You need C2FLR (testnet FLR) to: - Register custom instructions (one-time gas cost) - Fund the personal account with native tokens for LayerZero fees The actual bridging is triggered via XRPL payments and executed by the operator. **Q: What is the operator's role?** A: The operator monitors XRPL payments to a designated address, obtains FDC proofs, and relays transactions to the `MasterAccountController` on Flare. This service enables XRPL users to trigger Flare actions without holding FLR. **Q: Can I use this on mainnet?** A: This guide is for testnet. For mainnet deployment, update contract addresses, thoroughly test, and audit all code. ## Troubleshooting **Error: XRPL_SECRET not set in .env** - Solution: Add your XRPL testnet wallet secret to the `.env` file. **Error: No agents available** - Solution: No FAsset agents have sufficient free collateral. Try with fewer lots or wait for agents to free up capacity. **Error: Timeout waiting for reservation event** - Solution: The operator may be delayed. Check that the XRPL payment was successful and retry. **Error: Insufficient balance for bridging** - Solution: Ensure `AUTO_MINT_IF_NEEDED` is `true`, or manually mint FXRP to the personal account first. **Error: LayerZero fee insufficient** - Solution: Fund the personal account with more C2FLR for the LayerZero cross-chain fee. :::tip[Next Steps] To continue your FAssets development journey, you can: - Learn how to [mint FXRP manually](/fassets/developer-guides/fassets-mint) - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) - Explore [auto-redemption from Hyperliquid](/fxrp/oft/fxrp-autoredeem) - Read more about [Flare Smart Accounts](/smart-accounts/overview) ::: --- ## FAsset Auto-Redemption ## Overview In this guide, you will learn how to bridge FAssets (specifically FXRP) between Flare Testnet Coston2 and Hyperliquid using LayerZero's cross-chain messaging protocol, with support for **automatic redemption** - converting FXRP back to native XRP on the XRP Ledger in a single transaction. This guide covers four key functionalities: 1. **Bridging FXRP from Flare Testnet Coston2 to Hyperliquid EVM** (`bridgeToHyperEVM.ts`) - Transfer wrapped XRP tokens to Hyperliquid EVM for DeFi use. 2. **Bridging FXRP from Flare Testnet Coston2 to HyperCore** (`bridgeToHyperCore.ts`) - Transfer wrapped XRP tokens directly to HyperCore for spot trading. 3. **Auto-Redeem from Hyperliquid EVM to Underlying Asset** (`autoRedeemFromHyperEVM.ts`) - Send FXRP from Hyperliquid EVM back to Flare Testnet Coston2 and automatically redeem it for native XRP. 4. **Auto-Redeem from HyperCore to Underlying Asset** (`autoRedeemFromHyperCore.ts`) - Transfer FXRP from HyperCore spot wallet to HyperEVM, then automatically bridge and redeem to native XRP in one script. **Key technologies:** - [LayerZero OFT](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) (Omnichain Fungible Token) for cross-chain token transfers. OFT works by burning tokens on the source chain and minting equivalent tokens on the destination chain, enabling seamless movement of assets across different blockchains. - Flare's [FAsset](/fassets/overview) system for tokenizing non-smart contract assets. - [LayerZero Composer](https://docs.layerzero.network/v2/developers/evm/oft/composing) pattern for executing custom logic (like FAsset redemption) automatically when tokens arrive on the destination chain. - [Hyperliquid spotSend API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#spot-transfer) for transferring tokens between HyperCore and HyperEVM. A shared [`FAssetRedeemComposer`](https://coston2-explorer.flare.network/address/0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0?tab=contract) contract is pre-deployed on Coston2, so users do not need to deploy their own. This guide includes four TypeScript scripts (`bridgeToHyperEVM.ts`, `bridgeToHyperCore.ts`, `autoRedeemFromHyperEVM.ts`, and `autoRedeemFromHyperCore.ts`) that demonstrate the complete workflows. Clone the [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) to follow along. ## Getting Started: The Two-Step Process :::warning To successfully test the auto-redemption feature, you must run the scripts in the correct order. You must complete [Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first) (bridging TO Hyperliquid) before you can execute [Step 2](#step-2-auto-redeem-to-native-xrp-requires-step-1) (auto-redeeming FROM Hyperliquid). ::: ### Step 1: Bridge FXRP to Hyperliquid EVM (Required First) Run [`bridgeToHyperEVM.ts`](https://github.com/flare-foundation/flare-hardhat-starter/blob/main/scripts/fassets/bridgeToHyperEVM.ts) on **Flare Testnet Coston2** network to transfer your FXRP tokens from Coston2 to Hyperliquid EVM Testnet. This script does NOT involve auto-redemption - it simply moves your tokens to Hyperliquid where you can use them to prepare for the auto-redemption process. :::info You need FXRP tokens on Hyperliquid EVM before you can test the auto-redeem functionality. The auto-redeem script will fail if you don't have tokens there. ::: ```bash # Step 1: Bridge tokens TO Hyperliquid yarn hardhat run scripts/fassets/bridgeToHyperEVM.ts --network coston2 ``` ### Step 2: Auto-Redeem to Native XRP (Requires Step 1) Once you have FXRP tokens on Hyperliquid (from Step 1), run [`autoRedeemFromHyperEVM.ts`](https://github.com/flare-foundation/flare-hardhat-starter/blob/main/scripts/fassets/autoRedeemFromHyperEVM.ts) on **Hyperliquid Testnet** network to send them back to Flare Testnet Coston2 with automatic redemption to native XRP. This is the auto-redemption feature that converts FXRP back to native XRP on the XRP Ledger in a single transaction. ```bash # Step 2: Auto-redeem back to native XRP yarn hardhat run scripts/fassets/autoRedeemFromHyperEVM.ts --network hyperliquidTestnet ``` ## FAssetRedeemComposer Contract ### What It Is The [`FAssetRedeemComposer`](https://coston2-explorer.flare.network/address/0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0?tab=contract) is a shared LayerZero Composer contract **pre-deployed on Coston2** that automatically redeems FAssets to their underlying assets when tokens arrive via LayerZero's compose message feature. ### How It Works The composer implements the [ILayerZeroComposer](https://docs.layerzero.network/v2/developers/evm/composer/overview) interface and creates per-user `FAssetRedeemerAccount` contracts automatically: 1. **Receives Compose Message**: LayerZero endpoint calls [lzCompose()](https://docs.layerzero.network/v2/developers/evm/composer/overview#:~:text=/**%0A%20%20%20%20%20*%20%40notice%20Handles,messages%0A%20%20%20%20%7D%0A%7D) with the incoming OFT transfer and compose data. 2. **Decodes `RedeemComposeData`**: Extracts the redeemer's EVM address and their underlying XRP address from the compose message. 3. **Creates Redeemer Account**: Deploys (or reuses) a deterministic per-user `FAssetRedeemerAccount` contract via CREATE2. 4. **Deducts Composer Fee**: Takes a percentage fee (configurable per source chain, currently 1%) from the received FXRP. 5. **Transfers Tokens**: Sends the remaining FXRP to the redeemer account. 6. **Executes Redemption**: The redeemer account calls [`assetManager.redeem()`](/fassets/reference/IAssetManager#redeem) to burn FAssets and release underlying XRP. ### Compose Message Format The auto-redeem scripts encode the compose message using the `RedeemComposeData` struct: ```solidity struct RedeemComposeData { /// @notice EVM address that owns the per-redeemer account. address redeemer; /// @notice Underlying-chain redemption destination passed to the asset manager. string redeemerUnderlyingAddress; } ``` In TypeScript, this is encoded as: ```typescript const composeMsg = web3.eth.abi.encodeParameters( ["address", "string"], [redeemer, xrpAddress], ); ``` ### Executor Fee The `lzCompose` call must include native value to cover the executor fee on Coston2. This is set via `addExecutorLzComposeOption`: ```typescript const options = Options.newOptions() .addExecutorLzReceiveOption(EXECUTOR_GAS, 0) .addExecutorComposeOption(0, COMPOSE_GAS, COMPOSE_VALUE); ``` The current executor fee can be queried via `getExecutorData()` on the [composer contract](https://coston2-explorer.flare.network/address/0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0?tab=contract). ### Contract Source
View `FAssetRedeemComposer.sol` {FassetRedeemComposer} The full verified source is available on the [Coston2 explorer](https://coston2-explorer.flare.network/address/0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0?tab=contract).
## Bridge FXRP to Hyperliquid EVM (Step 1) ### What It Is **This is Step 1 of the two-step process** and is a **prerequisite** for testing the auto-redemption feature. This script bridges FXRP tokens from Flare Testnet Coston2 to Hyperliquid EVM Testnet using LayerZero's OFT Adapter pattern. It wraps existing ERC20 FAsset tokens into LayerZero OFT format for cross-chain transfer. :::warning This script does NOT perform auto-redemption. ::: Its purpose is to get your FXRP tokens onto Hyperliquid EVM Testnet so that: - You can use them for trading or DeFi on Hyperliquid, OR - You can run the auto-redeem script (Step 2) to convert them back to native XRP You must successfully complete this step before running the `autoRedeemFromHyperEVM.ts` script. ### How It Works #### Step-by-Step Process 1. **Get Asset Manager Info**: Retrieves fAsset address and lot size dynamically from the AssetManager contract using the `getAssetManagerFXRP` utility. 2. **Balance Check**: Verifies user has sufficient FTestXRP tokens. 3. **Token Approval**: - Approves OFT Adapter to spend FTestXRP. - Approves Composer (if needed for future operations). 4. **Build Send Parameters**: - Destination: Hyperliquid EVM Testnet (EndpointID for Hyperliquid Testnet: `40362`). - Recipient: Same address on destination chain. - Amount: Calculated from configured lots (default 1 lot, with 10% buffer for safety). - LayerZero options: Executor gas limit set to 200,000. 5. **Quote Fee**: Calculates the LayerZero cross-chain messaging fee. 6. **Execute Bridge**: Sends tokens via `oftAdapter.send()`. 7. **Confirmation**: Waits for transaction confirmation and provides tracking link to the LayerZero Explorer page. ### Prerequisites - **Balance Requirements**: - FTestXRP tokens (amount you want to bridge). - C2FLR tokens (for gas fees + LayerZero fees). You can get some from the Flare Testnet [faucet](https://faucet.flare.network/). - **Environment Setup**: - Private key configured in Hardhat for Flare Testnet Coston2 and Hyperliquid Testnet. ### Configuration Edit the `CONFIG` object in the script: ```typescript const CONFIG = { COSTON2_OFT_ADAPTER: "0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639", COSTON2_COMPOSER: process.env.COSTON2_COMPOSER || "", HYPERLIQUID_EID: EndpointId.HYPERLIQUID_V2_TESTNET, EXECUTOR_GAS: 200_000, BRIDGE_LOTS: "1", // Change this to your desired number of lots }; ``` ### How to Run **Run this script FIRST before attempting auto-redemption.** This gets your FXRP tokens onto Hyperliquid EVM. 1. **Install Dependencies**: ```bash yarn install ``` 2. **Configure Environment**: ```bash # .env file COSTON2_RPC_URL=https://coston2-api.flare.network/ext/C/rpc DEPLOYER_PRIVATE_KEY=your_private_key_here COSTON2_COMPOSER=0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 ``` 3. **Run the Script on Flare Testnet Coston2**: ```bash yarn hardhat run scripts/fassets/bridgeToHyperEVM.ts --network coston2 ``` 4. **Wait for Completion**: - Monitor the transaction on LayerZero Scan (link provided in output) - Allow 2-5 minutes for cross-chain delivery - Verify FXRP balance increased on Hyperliquid EVM before proceeding to Step 2 ### Expected Output ``` Using account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e 📋 Bridge Details: From: Coston2 To: Hyperliquid EVM Testnet Amount: 11.0 FXRP Recipient: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e Your FTestXRP balance: 100.0 1️⃣ Checking OFT Adapter token address... OFT Adapter's inner token: 0x8b4abA9C4BD7DD961659b02129beE20c6286e17F Expected token: 0x8b4abA9C4BD7DD961659b02129beE20c6286e17F Match: true Approving FTestXRP for OFT Adapter... OFT Adapter address: 0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639 Amount: 11.0 FXRP ✅ OFT Adapter approved Verified allowance: 22.0 FXRP 3️⃣ LayerZero Fee: 0.001234 C2FLR 4️⃣ Sending FXRP to Hyperliquid EVM Testnet... Transaction sent: 0xabc123... ✅ Confirmed in block: 12345678 🎉 Success! Your FXRP is on the way to Hyperliquid EVM Testnet! Track your transaction: https://testnet.layerzeroscan.com/tx/0xabc123... It may take a few minutes to arrive on Hyperliquid EVM Testnet. ```
View `bridgeToHyperEVM.ts` source code {bridgeToHyperEVM}
### Troubleshooting **Error: Insufficient FTestXRP balance** - Solution: Acquire FTestXRP tokens on Flare Testnet Coston2 [faucet](https://faucet.flare.network/coston2) or follow our Fasset minting [guide](/fassets/developer-guides/fassets-mint). **Error: Insufficient C2FLR for gas** - Solution: Get C2FLR from Flare Testnet Coston2 [faucet](https://faucet.flare.network/coston2). **Error: Transaction reverted** - Check that the OFT Adapter address matches the FTestXRP token. - Verify LayerZero endpoint is operational. - Ensure gas limits are sufficient. ## Bridge FXRP to HyperCore ### What It Is This script bridges FXRP tokens from Flare Testnet Coston2 directly to Hyperliquid HyperCore (the spot trading layer) using LayerZero's OFT Adapter with a compose message. Unlike `bridgeToHyperEVM.ts` which deposits tokens on HyperEVM, this script triggers automatic transfer to HyperCore where you can trade FXRP on Hyperliquid's spot market. :::info Understanding the Difference - **bridgeToHyperEVM.ts**: Deposits FXRP on HyperEVM (EVM layer) for DeFi use. - **bridgeToHyperCore.ts**: Deposits FXRP on HyperCore (trading layer) for spot trading. Choose based on whether you want to use FXRP in smart contracts (HyperEVM) or trade it (HyperCore). ::: ### How It Works #### Flow Diagram ```mermaid sequenceDiagram participant Dev as Developer (Coston2) participant OFT as OFT Adapter (Coston2) participant LZ as LayerZero participant EP as HyperEVM Endpoint participant Comp as HyperliquidComposer(shared, pre-deployed) participant HC as HyperCore Spot Wallet Dev->>OFT: 1. Send FXRP via OFT Adapterwith Compose Message OFT->>LZ: 2. lzSend() LZ->>EP: 3. Cross-chain message EP->>Comp: 4. lzReceive() + lzCompose() Comp->>HC: 5. Transfer to system address Note over HC: FXRP credited tospot wallet ``` #### Step-by-Step Process 1. **Validate Config**: Checks that `HYPERLIQUID_COMPOSER` is configured. 2. **Get Asset Manager Info**: Retrieves fAsset address and lot size dynamically from the AssetManager contract. 3. **Balance Check**: Verifies user has sufficient FTestXRP tokens. 4. **Token Approval**: Approves OFT Adapter to spend FTestXRP. 5. **Encode Compose Message**: Encodes `(uint256 amount, address recipient)` for HyperCore transfer. 6. **Build LayerZero Options**: - Executor gas for `lzReceive()`: 200,000 - Compose gas for `lzCompose()`: 300,000 7. **Build Send Parameters**: - Destination: HyperEVM (EndpointID for Hyperliquid Testnet: `40362`). - Recipient: HyperliquidComposer contract (not user address). - Amount: Calculated from configured lots (default 1 lot, with 10% buffer). 8. **Quote Fee**: Calculates the LayerZero cross-chain messaging fee. 9. **Execute Bridge**: Sends tokens via `oftAdapter.send()` with compose. 10. **HyperCore Credit**: Upon arrival, composer transfers tokens to HyperCore. ### Prerequisites - **Balance Requirements**: - FTestXRP tokens on Coston2 (amount you want to bridge). - C2FLR tokens (for gas fees + LayerZero fees). You can get some from the Flare Testnet [faucet](https://faucet.flare.network/). - **Deployed Contracts**: - HyperliquidComposer must be deployed on HyperEVM Testnet by you (see [Bridge FXRP to HyperCore](#bridge-fxrp-to-hypercore)). - Set `HYPERLIQUID_COMPOSER=0x...` in `.env` (your HyperEVM composer address). ### Configuration Edit the `CONFIG` object in the script: ```typescript const CONFIG = { COSTON2_OFT_ADAPTER: "0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639", HYPERLIQUID_COMPOSER: process.env.HYPERLIQUID_COMPOSER || "", HYPERLIQUID_EID: EndpointId.HYPERLIQUID_V2_TESTNET, EXECUTOR_GAS: 200_000, // Gas for receiving on HyperEVM COMPOSE_GAS: 300_000, // Gas for compose execution BRIDGE_LOTS: "1", // Change this to your desired number of lots }; ``` ### How to Run 1. **Deploy HyperliquidComposer** (first time only): ```bash yarn hardhat run scripts/fassets/deployHyperliquidComposer.ts --network hyperliquidTestnet ``` 2. **Configure Environment**: ```bash # .env file COSTON2_RPC_URL=https://coston2-api.flare.network/ext/C/rpc DEPLOYER_PRIVATE_KEY=your_private_key_here HYPERLIQUID_COMPOSER=0x... # Required! Set this after deploying the composer ``` 3. **Run the Script on Flare Testnet Coston2**: ```bash yarn hardhat run scripts/fassets/bridgeToHyperCore.ts --network coston2 ``` 4. **Wait for Completion**: - Monitor the transaction on LayerZero Scan (link provided in output). - Allow 2-5 minutes for cross-chain delivery. - Verify FXRP balance on HyperCore via Hyperliquid's UI or API. ### Expected Output ``` ✓ HyperliquidComposer configured: 0x123... Using account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e Token address: 0x8b4abA9C4BD7DD961659b02129beE20c6286e17F Token decimals: 6 📋 Bridge Details: From: Coston2 To: Hyperliquid HyperCore (via HyperEVM) Amount: 11.0 FXRP HyperCore Recipient: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e Your FTestXRP balance: 100.0 1️⃣ Checking OFT Adapter token address... OFT Adapter's underlying token: 0x8b4abA9C4BD7DD961659b02129beE20c6286e17F Expected token: 0x8b4abA9C4BD7DD961659b02129beE20c6286e17F Match: true Approving FTestXRP for OFT Adapter... OFT Adapter address: 0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639 Amount: 11.0 FXRP ✅ OFT Adapter approved Verified allowance: 11.0 FXRP 2️⃣ Compose message encoded for HyperCore transfer 3️⃣ LayerZero Fee: 0.001234 C2FLR 4️⃣ Sending FXRP to Hyperliquid HyperCore... Via HyperliquidComposer: 0x123... Transaction sent: 0xabc123... ✅ Confirmed in block: 12345678 🎉 Success! Your FXRP is on the way to Hyperliquid HyperCore! Track your transaction: https://testnet.layerzeroscan.com/tx/0xabc123... ⏳ The tokens will be automatically transferred to HyperCore once they arrive on HyperEVM. You can then trade them on the Hyperliquid DEX. ```
View `bridgeToHyperCore.ts` source code {bridgeToHyperCore}
### Troubleshooting **Error: HYPERLIQUID_COMPOSER not set** - Solution: Deploy the HyperliquidComposer contract on HyperEVM Testnet first. **Error: Insufficient FTestXRP balance** - Solution: Acquire FTestXRP tokens on Flare Testnet Coston2 [faucet](https://faucet.flare.network/coston2) or follow our Fasset minting [guide](/fassets/developer-guides/fassets-mint). **Error: Insufficient C2FLR for gas** - Solution: Get C2FLR from Flare Testnet Coston2 [faucet](https://faucet.flare.network/coston2). **Error: Tokens arrived on HyperEVM but not on HyperCore** - Check that the HyperliquidComposer is correctly configured. - Verify the compose message was properly encoded. - Check LayerZero Scan for compose execution status. ## Auto-Redeem from Hyperliquid EVM (Step 2) ### What It Is **This is Step 2 of the two-step process** and **requires you to have FXRP tokens on Hyperliquid EVM first** (from [Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first)). This script sends FXRP from Hyperliquid EVM Testnet back to Flare Testnet Coston2 with **automatic redemption** to native XRP on the XRP Ledger. It uses LayerZero's compose feature to trigger the `FAssetRedeemComposer` contract upon arrival. **Prerequisites:** - You must have FXRP OFT tokens on Hyperliquid EVM Testnet. - If you don't have FXRP on Hyperliquid, run `bridgeToHyperEVM.ts` first ([Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first)). - The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0`. ### How It Works #### Flow Diagram ```mermaid sequenceDiagram participant Dev as Developer (HyperEVM) participant OFT as FXRP OFT (HyperEVM) participant LZ as LayerZero participant EP as Coston2 Endpoint participant Comp as FAssetRedeemComposer(shared, pre-deployed) participant Acct as FAssetRedeemerAccount(per-user, CREATE2) participant XRP as XRP Ledger Address Dev->>OFT: 1. send() with Compose MessageData: (redeemer, xrpAddress) OFT->>LZ: 2. lzSend() LZ->>EP: 3. Cross-chain message EP->>Comp: 4. lzReceive() + lzCompose() Comp->>Comp: 5. Deduct composer fee Comp->>Acct: 6. Transfer FXRP to redeemer account Acct->>Acct: 7. assetManager.redeem()Burns FXRP Acct-->>XRP: 8. XRP sent to underlying address ``` #### Step-by-Step Process 1. **Validate Setup**: - Checks that `COSTON2_COMPOSER` is configured. - Gets the signer account. 2. **Connect to FXRP OFT**: Gets the OFT contract on Hyperliquid using the `FXRPOFT` artifact. 3. **Prepare Redemption Parameters**: - Number of lots to send (default: 1 lot). - XRP address to receive native XRP. - Redeemer address (EVM address). 4. **Encode Compose Message**: - Encodes `(address redeemer, string xrpAddress)` matching the `RedeemComposeData` struct. - This tells the composer what to do when tokens arrive. 5. **Build LayerZero Options**: - Executor gas for `lzReceive()`: 1,000,000 - Compose gas for `lzCompose()`: 1,000,000 6. **Build Send Parameters**: - Destination: Coston2 (Endpoint ID: `40294`). - Recipient: FAssetRedeemComposer contract. - Amount: Calculated from configured lots (default 1 lot). - Compose message included 7. **Check Balance**: Verifies user has sufficient FXRP OFT. 8. **Quote Fee**: Calculates LayerZero messaging fee. 9. **Execute Send**: Sends FXRP with compose message. 10. **Auto-Redemption**: On arrival, composer automatically redeems to XRP. ### Prerequisites - **Network**: Must run on Hyperliquid EVM Testnet. - **Balance Requirements**: - FXRP OFT tokens on Hyperliquid (amount you want to redeem). - HYPE tokens (for gas fees + LayerZero fees). - **Deployed Contracts**: - The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0`. - Set `COSTON2_COMPOSER=0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0` in `.env`. ### Configuration Edit the `CONFIG` object in the script: ```typescript const CONFIG = { HYPERLIQUID_FXRP_OFT: process.env.HYPERLIQUID_FXRP_OFT || "0x14bfb521e318fc3d5e92A8462C65079BC7d4284c", // Shared FAssetRedeemComposer (pre-deployed on Coston2) COSTON2_COMPOSER: process.env.COSTON2_COMPOSER || "0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0", COSTON2_EID: EndpointId.FLARE_V2_TESTNET, EXECUTOR_GAS: 1_000_000, // Gas for receiving on Coston2 COMPOSE_GAS: 1_000_000, // Gas for compose execution // Native value forwarded to cover executor fee on Coston2 COMPOSE_VALUE: BigInt("1000000000000000"), // 0.001 ETH SEND_LOTS: "1", // Number of lots to redeem XRP_ADDRESS: "rpHuw4bKSjonKRrKKVYYVedg1jyPrmp", // Your XRP address }; ``` ### How to Run **PREREQUISITE: You must have FXRP tokens on Hyperliquid EVM Testnet before running this script.** If you don't have FXRP on Hyperliquid yet: - Run `bridgeToHyperEVM.ts` first ([Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first)) - Wait for the bridge transaction to complete (2-5 minutes) - Verify your FXRP balance on Hyperliquid EVM before proceeding Once you have FXRP on Hyperliquid: 2. **Configure Environment**: ```bash # .env file HYPERLIQUID_TESTNET_RPC_URL=https://api.hyperliquid-testnet.xyz/evm DEPLOYER_PRIVATE_KEY=your_private_key_here COSTON2_COMPOSER=0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 HYPERLIQUID_FXRP_OFT=0x14bfb521e318fc3d5e92A8462C65079BC7d4284c ``` 3. **Update XRP Address**: - Edit `CONFIG.XRP_ADDRESS` in the script to your XRP ledger address - This is where you'll receive the native XRP - Must be a valid XRP address format (starts with 'r') 4. **Run the Script on Hyperliquid Testnet**: ```bash yarn hardhat run scripts/fassets/autoRedeemFromHyperEVM.ts --network hyperliquidTestnet ``` ### Expected Output ``` Using account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e ✓ Composer configured: 0x123... 📋 Redemption Parameters: Amount: 10.0 FXRP XRP Address: rpHuw4bKSjonKRrKKVYYVedg1jyPrmp Redeemer: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e Connecting to FXRP OFT on Hyperliquid EVM: 0x14bfb521e318fc3d5e92A8462C65079BC7d4284c ✓ Connected to FXRP OFT: 0x14bfb521e318fc3d5e92A8462C65079BC7d4284c OFT address: 0x14bfb521e318fc3d5e92A8462C65079BC7d4284c Compose message encoded 💰 Current FXRP balance: 25.0 Sufficient balance 💵 LayerZero Fee: 0.002456 HYPE 🚀 Sending 10.0 FXRP to Coston2 with auto-redeem... Target composer: 0x123... Underlying address: rpHuw4bKSjonKRrKKVYYVedg1jyPrmp ✓ Transaction sent: 0xdef456... Waiting for confirmation... ✅ Confirmed in block: 9876543 🎉 Success! Your FXRP is on the way to Coston2! 📊 Track your cross-chain transaction: https://testnet.layerzeroscan.com/tx/0xdef456... ⏳ The auto-redeem will execute once the message arrives on Coston2. XRP will be sent to: rpHuw4bKSjonKRrKKVYYVedg1jyPrmp ```
View `autoRedeemFromHyperEVM.ts` source code {autoRedeemFromHyperEVM}
## Auto-Redeem from HyperCore ### What It Is This script provides a complete auto-redemption flow starting from HyperCore (Hyperliquid's spot trading layer) to native XRP on the XRP Ledger. Unlike the HyperEVM flow which assumes you already have FXRP on HyperEVM, this script handles the full journey: **HyperCore → HyperEVM → Coston2 → XRP Ledger**. :::info Understanding HyperCore vs HyperEVM **HyperCore** is Hyperliquid's high-performance spot trading layer where tokens are held in spot wallets. **HyperEVM** is Hyperliquid's EVM-compatible execution environment where tokens exist as ERC-20s. Tokens must be transferred from HyperCore to HyperEVM before they can be bridged via LayerZero. ::: **Prerequisites:** - You must have FXRP tokens in your HyperCore spot wallet. - You need HYPE tokens on HyperEVM for LayerZero fees. - The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0`. ### How It Works #### Flow Diagram ```mermaid sequenceDiagram participant Dev as Developer (HyperCore) participant API as Hyperliquid API participant OFT as FXRP OFT (HyperEVM) participant LZ as LayerZero participant EP as Coston2 Endpoint participant Comp as FAssetRedeemComposer(shared, pre-deployed) participant Acct as FAssetRedeemerAccount(per-user, CREATE2) participant XRP as XRP Ledger Address Dev->>API: 1. spotSend to system address(EIP-712 signed) API->>OFT: 2. Tokens appear on HyperEVM Dev->>OFT: 3. send() with Compose MessageData: (redeemer, xrpAddress) OFT->>LZ: 4. lzSend() LZ->>EP: 5. Cross-chain message EP->>Comp: 6. lzReceive() + lzCompose() Comp->>Comp: 7. Deduct composer fee Comp->>Acct: 8. Transfer FXRP to redeemer account Acct->>Acct: 9. assetManager.redeem()Burns FXRP Acct-->>XRP: 10. XRP sent to underlying address ``` #### Step-by-Step Process 1. **Validate Setup**: - Checks that `COSTON2_COMPOSER` is configured. - Gets the signer account. 2. **Check HyperCore Balance**: - Queries HyperCore spot balance via the `/info` API endpoint. - Verifies sufficient FXRP is available. 3. **Transfer from HyperCore to HyperEVM**: - Prepares an EIP-712 signed `spotSend` message. - Sends to the FXRP system address (`0x20000000000000000000000000000000000005a3`). - Waits for the transfer to settle on HyperEVM. 4. **Connect to FXRP OFT**: Gets the OFT contract on HyperEVM. 5. **Prepare Redemption Parameters**: - Number of lots to send (default: 1 lot). - XRP address to receive native XRP. - Redeemer address (EVM address). 6. **Check HyperEVM Balance**: Verifies tokens arrived from HyperCore. 7. **Encode Compose Message**: - Encodes `(address redeemer, string xrpAddress)` matching the `RedeemComposeData` struct. - This tells the composer what to do when tokens arrive. 8. **Build LayerZero Options**: - Executor gas for `lzReceive()`: 1,000,000 - Compose gas for `lzCompose()`: 1,000,000 9. **Quote Fee**: Calculates LayerZero messaging fee. 10. **Execute Send**: Sends FXRP with compose message to Coston2. 11. **Auto-Redemption**: On arrival, composer automatically redeems to XRP. ### Understanding HyperCore Transfers The script uses Hyperliquid's `spotSend` API to transfer tokens from HyperCore to HyperEVM. This requires EIP-712 signing with specific parameters. #### EIP-712 Domain ```typescript const EIP712_DOMAIN = { name: "HyperliquidSignTransaction", version: "1", chainId: 42161, // Arbitrum chainId - required by Hyperliquid API verifyingContract: "0x0000000000000000000000000000000000000000", }; ``` :::warning Arbitrum ChainId Requirement Hyperliquid's API requires Arbitrum's chainId (42161) for all EIP-712 signatures, regardless of the actual chain. This is a legacy requirement from when Hyperliquid settled on Arbitrum. ::: #### System Address To transfer tokens from HyperCore to HyperEVM, you send them to a special system address. Each token has a unique system address based on its token index. ```typescript // System address for FXRP on testnet (token index 1443 = 0x5A3) FXRP_SYSTEM_ADDRESS: "0x20000000000000000000000000000000000005a3"; ``` ### Prerequisites - **Network**: Must run on Hyperliquid EVM Testnet. - **Balance Requirements**: - FXRP tokens in your HyperCore spot wallet (amount you want to redeem). - HYPE tokens on HyperEVM (for gas fees + LayerZero fees). - **Deployed Contracts**: - The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0`. - Set `COSTON2_COMPOSER=0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0` in `.env`. ### Configuration Edit the `CONFIG` object in the script: ```typescript const CONFIG = { // Testnet config HYPERLIQUID_API: process.env.HYPERLIQUID_API || "https://api.hyperliquid-testnet.xyz", HYPERLIQUID_FXRP_OFT: process.env.HYPERLIQUID_FXRP_OFT || "0x14bfb521e318fc3d5e92A8462C65079BC7d4284c", // System address for FXRP on testnet (token index 1443 = 0x5A3) FXRP_SYSTEM_ADDRESS: "0x20000000000000000000000000000000000005a3", FXRP_TOKEN_ID: "FXRP:0x2af78df5b575b45eea8a6a1175026dd6", // Shared FAssetRedeemComposer (pre-deployed on Coston2) COSTON2_COMPOSER: process.env.COSTON2_COMPOSER || "0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0", COSTON2_EID: EndpointId.FLARE_V2_TESTNET, EXECUTOR_GAS: 1_000_000, // Gas for receiving on Coston2 COMPOSE_GAS: 1_000_000, // Gas for compose execution // Native value forwarded to cover executor fee on Coston2 COMPOSE_VALUE: BigInt("1000000000000000"), // 0.001 HYPE SEND_LOTS: "1", // Number of lots to redeem XRP_ADDRESS: process.env.XRP_ADDRESS || "rpHuw4bKSjonKRrKKVYYVedg1jyPrmp", HYPERLIQUID_CHAIN: "Testnet", }; ``` ### How to Run **PREREQUISITE: You must have FXRP tokens in your HyperCore spot wallet before running this script.** 2. **Configure Environment**: ```bash # .env file HYPERLIQUID_TESTNET_RPC_URL=https://api.hyperliquid-testnet.xyz/evm DEPLOYER_PRIVATE_KEY=your_private_key_here HYPERLIQUID_COMPOSER=0x... # Composer deployed on HyperEVM by you (see Step 1.1) HYPERLIQUID_FXRP_OFT=0x14bfb521e318fc3d5e92A8462C65079BC7d4284c XRP_ADDRESS=rYourXRPAddressHere # Your XRP ledger address ``` 3. **Ensure you have FXRP on HyperCore**: - Bridge FXRP to HyperCore using `bridgeToHyperEVM.ts` followed by a transfer to HyperCore. - Or acquire FXRP on Hyperliquid's spot market. 4. **Run the Script on Hyperliquid Testnet**: ```bash yarn hardhat run scripts/fassets/autoRedeemFromHyperCore.ts --network hyperliquidTestnet ``` ### Expected Output ``` ============================================================ FXRP Auto-Redemption from HyperCore HyperCore → HyperEVM → Coston2 → XRP Ledger ============================================================ Using account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e ✓ Coston2 Composer configured: 0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 📊 Checking HyperCore spot balance... HyperCore FXRP balance: 25.0 📤 Step 1: Transferring FXRP from HyperCore to HyperEVM... Amount: 10 FXRP Destination (system address): 0x20000000000000000000000000000000000005a3 ✅ HyperCore → HyperEVM transfer initiated Response: {"status":"ok","response":{"type":"default"}} Waiting for transfer to settle on HyperEVM... Connecting to FXRP OFT on HyperEVM: 0x14bfb521e318fc3d5e92A8462C65079BC7d4284c ✓ Connected to FXRP OFT Token decimals: 6 📋 Redemption Parameters: Amount: 10.0 FXRP XRP Address: rpHuw4bKSjonKRrKKVYYVedg1jyPrmp Redeemer: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e 💰 HyperEVM FXRP balance: 10.0 ✓ Sufficient balance on HyperEVM Compose message encoded for auto-redemption 💵 LayerZero Fee: 0.003456 HYPE 📤 Step 2: Sending FXRP from HyperEVM to Coston2 with auto-redeem... Amount: 10.0 FXRP Target composer: 0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 XRP destination: rpHuw4bKSjonKRrKKVYYVedg1jyPrmp ✓ Transaction sent: 0xabc123... ✅ Confirmed in block: 9876543 🎉 Success! Auto-redemption initiated! 📊 Track your cross-chain transaction: https://testnet.layerzeroscan.com/tx/0xabc123... ⏳ The auto-redeem will execute once the message arrives on Coston2. XRP will be sent to: rpHuw4bKSjonKRrKKVYYVedg1jyPrmp ```
View `autoRedeemFromHyperCore.ts` source code {autoRedeemFromHyperCore}
### Troubleshooting **Error: Insufficient FXRP on HyperCore** - Solution: Bridge FXRP to HyperCore first or acquire FXRP on Hyperliquid's spot market. **Error: HyperCore transfer failed** - Check that your wallet is properly configured with Hyperliquid. - Verify the EIP-712 signature is being generated correctly. - Ensure the FXRP_TOKEN_ID matches the correct token on the network. **Error: Insufficient FXRP balance on HyperEVM** - The transfer from HyperCore may still be pending. - Wait a few more seconds and try again. - Check your HyperEVM balance directly. **Error: Insufficient HYPE for gas** - Get HYPE tokens from Hyperliquid testnet [faucet](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/testnet-faucet). ## FAQ **Q: How long does bridging take?** A: Typically 2-5 minutes for LayerZero message delivery + execution time. **Q: What's the minimum amount I can bridge?** A: Any amount for bridging to Hyperliquid. For auto-redeem, minimum is 1 [lot](https://dev.flare.network/fassets/minting#lots) (10 FXRP for XRP). **Q: Can I bridge to a different address?** A: Yes, edit the `recipientAddress` parameter in the scripts. **Q: What happens if compose execution fails?** A: Tokens will be stuck in the composer. Owner can recover using `recoverTokens()`. **Q: Can I use this on mainnet?** A: This is designed for testnet. For mainnet, update contract addresses, thoroughly test, and audit all code. **Q: How do I get FTestXRP on Flare Testnet Coston2?** A: Use Flare's FAsset minting process via the AssetManager contract. **Q: What if I don't have HYPE tokens?** A: Get them from Hyperliquid testnet [faucet](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/testnet-faucet) or DEX. **Q: What's the difference between HyperCore and HyperEVM?** A: HyperCore is Hyperliquid's high-performance spot trading layer where tokens are held in spot wallets. HyperEVM is Hyperliquid's EVM-compatible execution environment where tokens exist as ERC-20s. You need to transfer tokens from HyperCore to HyperEVM before bridging via LayerZero. **Q: Why does the HyperCore transfer use Arbitrum's chainId?** A: Hyperliquid's API requires Arbitrum's chainId (42161) for all EIP-712 signatures. This is a legacy requirement from when Hyperliquid settled on Arbitrum. ## Discovering Available Bridge Routes The `getOftPeers.ts` utility script discovers all configured LayerZero peers for the FXRP OFT Adapter on Flare Testnet Coston2. It scans all LayerZero V2 testnet endpoints to find which EVM chains have been configured as valid bridge destinations. Before bridging FXRP to another chain, you need to know which chains are supported. This script: - Automatically discovers all configured peer addresses - Shows which EVM chains you can bridge FXRP to/from - Provides the peer contract addresses for each chain - Outputs results in both human-readable and JSON formats #### How It Works 1. **Loads V2 Testnet Endpoints**: Dynamically retrieves all LayerZero V2 testnet endpoint IDs from the [`@layerzerolabs/lz-definitions`](https://www.npmjs.com/package/@layerzerolabs/lz-definitions) package. 2. **Queries Peers**: For each endpoint, calls the `peers()` [function](https://coston2-explorer.flare.network/address/0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639?tab=read_write_proxy&source_address=0x82BC5e114D0843160be1cEaB8196Ba91f87473FE#0xbb0b6a53) on the OFT Adapter contract. 3. **Filters Results**: Only shows endpoints that have a non-zero peer address configured. 4. **Formats Output**: Displays results in a table format and as JSON for programmatic use. #### How to Run ```bash yarn hardhat run scripts/layerZero/getOFTPeers.ts --network coston2 ``` #### Expected Output ``` === FXRP OFT Adapter Peers Discovery === OFT Adapter: 0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639 Network: Coston2 (Flare Testnet) Scanning 221 LayerZero V2 Testnet endpoints... ✅ Bsc (EID: 40102): 0xac7c4a07670589cf83b134a843bfe86c45a4bf4e ✅ Sepolia (EID: 40161): 0x81672c5d42f3573ad95a0bdfbe824faac547d4e6 ✅ Hyperliquid (EID: 40362): 0x14bfb521e318fc3d5e92a8462c65079bc7d4284c ============================================================ SUMMARY: Configured Peers ============================================================ Found 3 configured peer(s): | Chain | EID | Peer Address | |-------|-----|--------------| | Bsc | 40102 | 0xac7c4a07670589cf83b134a843bfe86c45a4bf4e | | Sepolia | 40161 | 0x81672c5d42f3573ad95a0bdfbe824faac547d4e6 | | Hyperliquid | 40362 | 0x14bfb521e318fc3d5e92a8462c65079bc7d4284c | --- Available Routes --- You can bridge FXRP to/from the following chains: • Bsc • Sepolia • Hyperliquid ``` Once you've identified available peers, you can: 1. **Bridge to any discovered chain**: Update the destination EID in `bridgeToHyperEVM.ts` to target a different chain 2. **Verify peer addresses**: Use the peer addresses to interact with OFT contracts on other chains 3. **Build integrations**: Use the JSON output to programmatically determine available routes
View `getOftPeers.ts` source code {getOftPeers}
:::tip Next Steps To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint) - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) - Explore [FAssets system settings](/fassets/operational-parameters) ::: --- ## Omnichain Fungible Token (OFT) ## Overview FXRP token is deployed as an [Omnichain Fungible Token (OFT)](https://docs.layerzero.network/v2/home/token-standards/oft-standard) using the [LayerZero](https://layerzero.network/) protocol. This enables FXRP to be transferred across multiple blockchain networks while maintaining fungibility and a unified total supply. The OFT standard allows tokens to be natively transferred between chains without the need for wrapped versions or liquidity pools, providing a seamless cross-chain experience for FXRP holders. ## How It Works On Flare, FXRP uses an **OFT Adapter** contract that locks tokens when bridging to other chains. On destination chains, native OFT contracts mint and burn tokens as they are transferred in and out. :::info Oft Documentation For more details on how the OFT standard works, see the [LayerZero OFT documentation](https://docs.layerzero.network/v2/home/token-standards/oft-standard). ::: ## DVN Security Stack LayerZero V2 uses **Decentralized Verifier Networks (DVNs)** to verify cross-chain message integrity before execution. Each app configures its own security stack by selecting one or more DVNs, instead of relying on a single shared verifier. For FXRP OFT routes that include Flare, the following DVNs are used: | DVN Provider | Flare DVN Address | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LayerZero Labs | [`0x9c061c9a4782294eef65ef28cb88233a987f4bdd`](https://layerzeroscan.com/api/explorer/flare/address/0x9c061c9a4782294eef65ef28cb88233a987f4bdd) | | Nethermind | [`0x9bcd17a654bffaa6f8fea38d19661a7210e22196`](https://layerzeroscan.com/api/explorer/flare/address/0x9bcd17a654bffaa6f8fea38d19661a7210e22196) | | Canary | [`0xd791948db16ab4373fa394b74c727ddb7fb02520`](https://layerzeroscan.com/api/explorer/flare/address/0xd791948db16ab4373fa394b74c727ddb7fb02520) | | Horizen | [`0xeaa5a170d2588f84773f965281f8611d61312832`](https://layerzeroscan.com/api/explorer/flare/address/0xeaa5a170d2588f84773f965281f8611d61312832) | :::info DVN Documentation See [LayerZero DVN Providers documentation](https://docs.layerzero.network/v2/deployments/dvn-addresses) for deployment details, and [LayerZero V2: Explaining DVNs](https://layerzero.network/blog/layerzero-v2-explaining-dvns) for the conceptual model. ::: ## Bridge FXRP You can bridge FXRP between supported chains using [Stargate Finance](https://stargate.finance/?srcChain=flare&srcToken=0xAd552A648C74D49E10027AB8a618A3ad4901c5bE) protocol. ## Deployments The following table lists the FXRP OFT contract addresses across supported chains: ### Mainnet | Chain | Contract Type | Address | | ---------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flare | OFT Adapter | [`0xd70659a6396285BF7214d7Ea9673184e7C72E07E`](https://flare-explorer.flare.network/address/0xd70659a6396285BF7214d7Ea9673184e7C72E07E) | | HyperEVM | OFT | [`0xd70659a6396285BF7214d7Ea9673184e7C72E07E`](https://hyperevmscan.io/address/0xd70659a6396285BF7214d7Ea9673184e7C72E07E) | | HyperCore | OFT | [`0x200000000000000000000000000000000000016f`](https://app.hyperliquid.xyz/explorer/address/0x200000000000000000000000000000000000016f) | | Ethereum Mainnet | OFT | [`0xce6170ea245dc8d1f275a710a062b70f125f0110`](https://etherscan.io/address/0xce6170ea245dc8d1f275a710a062b70f125f0110) | | Base | OFT | [`0xCE6170EA245dC8D1f275A710a062b70f125F0110`](https://basescan.org/address/0xCE6170EA245dC8D1f275A710a062b70f125F0110) | | Monad | OFT | [`0xCE6170EA245dC8D1f275A710a062b70f125F0110`](https://monadscan.com/address/0xCE6170EA245dC8D1f275A710a062b70f125F0110) | ### Testnet The guides under [Guides](#guides) target the Coston2 ↔ Hyperliquid testnet route. The relevant deployments are: | Chain | Contract Type | Address | | ------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Coston2 | OFT Adapter | [`0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639`](https://coston2-explorer.flare.network/address/0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639) | | Coston2 | FAssetRedeemComposer | [`0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0`](https://coston2-explorer.flare.network/address/0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0) | | Hyperliquid Testnet | FXRP OFT | [`0x14bfb521e318fc3d5e92A8462C65079BC7d4284c`](https://app.hyperliquid-testnet.xyz/explorer/address/0x14bfb521e318fc3d5e92A8462C65079BC7d4284c) | ## Guides Learn how to bridge FXRP across chains using LayerZero's OFT standard, including automatic redemption to native XRP and cross-chain minting. :::tip[What's next] Learn more about FAssets and how to use the OFT with the FXRP token: - [FAssets Overview](/fassets/overview) ::: --- ## FXRP FXRP is the [FAsset](/fassets/overview) representation of XRP on the Flare network. It is an [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token that represents XRP bridged from the XRP Ledger (XRPL) to Flare. FXRP is powered by the [FAssets](/fassets/overview) system, a trustless, over-collateralized bridge infrastructure that enables the creation of wrapped tokens for assets from non-smart contract networks. ### Key Features - **ERC-20 Standard:** As an [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token, FXRP is compatible with all EVM-compatible wallets, smart contracts, and DeFi protocols on Flare, giving you full interoperability across the ecosystem. - **Trustless Bridge:** Your FXRP is backed by the [FAssets](/fassets/overview) system, which uses the [Flare Data Connector (FDC)](/fdc/overview) to verify XRPL transactions, eliminating the need for trusted intermediaries. - **Always Redeemable:** You can redeem your FXRP for the original XRP at any time, maintaining complete control over your assets. - **DeFi Integration:** Use your FXRP in various DeFi applications, including lending, borrowing, yield farming, and liquidity provision across Flare's ecosystem. - **Yield Opportunities:** Earn yield on your FXRP through protocols like [Firelight](/fxrp/firelight/status), which provides [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) compliant vaults for FXRP holders. ## How to Acquire FXRP There are four primary ways to acquire FXRP: ### Testnet Faucet On **Flare Testnet Coston2**, you can testnet FXRP directly from the [Coston2 Faucet](https://faucet.flare.network/coston2) — no minting required. This is the fastest way to start testing FAssets integrations. ### Using Minting dApp The most direct way to acquire FXRP is by minting it from native XRP on the XRP Ledger (XRPL) using a minting dApp. You can mint FXRP on either of the following dApps: - [https://fasset.oracle-daemon.com/flare](https://fasset.oracle-daemon.com/flare) - [https://fassets.au.cc](https://fassets.au.cc) ### Mint FXRP via Code You can also mint FXRP programmatically using smart contracts and code. This approach is ideal for developers who want to integrate FXRP minting into their applications or automate the process for their users. For complete code examples and step-by-step instructions, see the [FAssets minting guide](/fassets/developer-guides/fassets-mint). ### Swap Other Tokens for FXRP You can also acquire FXRP by swapping other tokens (such as USDT0, FLR, or other ERC-20 tokens) on decentralized exchanges (DEXs) on Flare. For example, you can swap USDT0 to FXRP using SparkDEX (Uniswap V3) as described in the [swap guide](/fxrp/token-interactions/usdt0-fxrp-swap). ## Operational Parameters Review the key [FXRP operational parameters](/fxrp/parameters), including minting caps, lot sizes, and redemption fees across all supported networks. For the complete list of all FAssets parameters, see the [FAssets Operational Parameters](/fassets/operational-parameters) page. ## FXRP Token Interactions Guides for working directly with the FXRP token. ## Omnichain Fungible Token FXRP is deployed as an [Omnichain Fungible Token (OFT)](/fxrp/oft) using LayerZero, enabling cross-chain transfers. ## Firelight [Firelight](https://firelight.finance) is a yield-generating protocol built on top of FXRP. It provides [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) compliant vault that allows users to deposit FXRP and earn yield through the [FAssets](/fassets/overview) system. Firelight vaults operate on a [period-based logic](https://docs.firelight.finance/technical-documents#period-based-logic), where deposits and withdrawals are processed at specific intervals. Users receive vault shares representing their proportional ownership of the vault's assets. ## How Firelight Vaults Work This diagram shows the flow of operations for a user interacting with a Firelight vault. ```mermaid flowchart TB UserFXRP[("User's FXRP")] Deposit[deposit] Mint[mint] Shares[("Vault shares")] UserFXRP --> Deposit --> Shares UserFXRP --> Mint --> Shares Shares --> Withdraw[withdraw] Shares --> Redeem[redeem] Withdraw --> Request["Withdrawal request(current period)"] Redeem --> Request Request --> Wait["Wait for period end"] Wait --> Claim[claimWithdraw] Claim --> FXRP[("FXRP to user")] ``` The flow of operations for a user interacting with a Firelight vault is as follows: - **Deposit / Mint:** Two ways to get vault shares. Call `deposit(assets)` to deposit FXRP and receive the corresponding shares, or `mint(shares)` to specify the shares you want and pay the required FXRP. - **Withdraw / Redeem:** Both create a withdrawal request for the current period. Call `withdraw(assets)` to request a given amount of assets, or `redeem(shares)` to burn shares and request the equivalent assets. Shares are burned when you redeem. - **Claim:** After the period ends, call `claimWithdraw()` to receive your FXRP. Use the [Get Vault Status](/fxrp/firelight/status) and [Claim Withdrawals](/fxrp/firelight/claim) guides to find and claim completed periods. :::tip[What's next] - Learn more about [FAssets](/fassets/overview) and how the system works. - Explore how to [mint FXRP](/fassets/developer-guides/fassets-mint) from XRP. - Discover how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) back to XRP. ::: ## Upshift Vaults [Upshift](https://upshift.finance) is a yield-generating protocol that supports FXRP through strategy-driven vaults on Flare. It provides [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style vaults that allow users to deposit FXRP and earn yield generated from on-chain DeFi strategies, while abstracting away strategy execution and risk management. Upshift vaults support both instant and requested redemptions, depending on available liquidity. Users receive vault shares that represent their proportional ownership of the vault's assets, with yield accruing as share value increases over time. The [Upshift vault](https://app.upshift.finance/pools/14/0x373D7d201C8134D4a2f7b5c63560da217e3dEA28) is part of the XRP finance stack on Flare Network. The example scripts in the guides below interact with the Coston2 testnet [vault](https://coston2-explorer.flare.network/address/0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81). It inherits the transparency model of the [FAssets](/fassets/overview) system: minting and redemption are proof-backed, and vault interactions are recorded on-chain. - **Funds are on-chain:** User deposits, vault balances, withdrawal requests, and claims are executed as smart contract state changes on Flare. You can verify all vault holdings on [DeBank](https://debank.com/profile/0xEDb7B1e92B8D3621b46843AD024949F10438374B) portfolio tracker. - **User ownership is verifiable:** A user's position is represented directly on-chain and can be queried from smart contracts. - **Instruction flow is auditable:** XRPL-triggered actions are executed on Flare via [Smart Accounts](/smart-accounts/overview) with [Flare Data Connector](/fdc/overview) backed verification and onchain execution. You can verify all smart account activity on the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/smart-accounts). ## How Upshift Vaults Work This diagram shows the flow of operations for a user interacting with an Upshift vault. ```mermaid flowchart TB UserFXRP[("User's FXRP")] Deposit[deposit] LP[("LP Tokens (vault shares)")] UserFXRP --> Deposit --> LP LP --> InstantRedeem[instantRedeem] InstantRedeem -->|"instant redemption fee applies"| InstantFXRP[("FXRP to user")] LP --> RequestRedeem[requestRedeem] RequestRedeem --> Epoch["Withdrawal request(epoch: year, month, day)"] Epoch --> Wait["Wait lag duration"] Wait --> Claim[claim] Claim -->|"withdrawal fee applies"| RequestedFXRP[("FXRP to user")] ``` The flow of operations for a user interacting with an Upshift vault is as follows: - **Deposit:** Transfer FXRP to the vault and receive the corresponding LP tokens (shares). Yield accrues as share value increases. - **Instant redeem:** Burn the corresponding LP tokens and receive FXRP immediately via the `instantRedeem()` function. In this case you need to pay the instant redemption fee. Use this option when you need liquidity right away. - **Requested redeem:** Lock the LP tokens with `requestRedeem()` and create a withdrawal request for the current epoch. After the lag duration, call `claim(year, month, day, receiver)` to receive FXRP. Lower fee than instant redeem. Use this option when you can wait. :::tip[What's next] - Learn more about [FAssets](/fassets/overview) and how the system works. - Explore how to [mint FXRP](/fassets/developer-guides/fassets-mint) from XRP. - Discover how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) back to XRP. ::: --- ## FXRP Operational Parameters This page lists the current values for key parameters relevant to FXRP on **Flare Mainnet** and **Flare Testnet Coston2**. For the complete list of FAssets operational parameters, see the [FAssets Operational Parameters](/fassets/operational-parameters) page. ## Minting and Redemption :::tip[What's next] Explore our [FXRP address](/fxrp/token-interactions/fxrp-address), [minting](/fassets/developer-guides/fassets-mint), and [redemption](/fassets/developer-guides/fassets-redeem) guides to get started with FXRP. For the complete list of all FAssets parameters including collateral ratios, liquidation, and agent settings, see the [FAssets Operational Parameters](/fassets/operational-parameters) page. ::: --- ## Get FXRP Address ## Overview In this guide, you will learn how to get the [FXRP](/fassets/reference) address for the FAssets system. FXRP is the ERC-20 representation of XRP on the Flare network, enabling you to use XRP in smart contracts and DeFi applications. ## Prerequisites Before you begin, make sure you have: - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) installed and configured. - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) installed in your project. - Basic understanding of [FAssets](/fassets/overview) and how they work. ## Understanding FXRP Address The FXRP address is the contract address of the [ERC-20 token](https://ethereum.org/developers/docs/standards/tokens/erc-20/) that represents XRP on the Flare network and is essential for: - Interacting with FXRP in smart contracts. - Transferring FXRP tokens. - Participating in DeFi protocols that use FXRP. ## Getting the FXRP Address To get the FXRP address, you need to interact with the `AssetManager` contract using the [`IAssetManager`](/fassets/reference/IAssetManager) interface and call the [`fAsset`](/fassets/reference/IAssetManager#fasset) function. This function returns the address of the FAsset token contract (ERC-20) that is managed by this asset manager instance. ### Script Code The following TypeScript script demonstrates how to retrieve the FXRP address: {FAssetsGetFxrpScript} ### Code Explanation The script: 1. Connects to the `AssetManager` contract using the [`IAssetManager`](/fassets/reference/IAssetManager) interface. 2. Calls the [`fAsset`](/fassets/reference/IAssetManager#fasset) function to get the FXRP token address. 3. Logs the address to the console. :::info The `getFXRPAssetManager` function is a utility function that is included in the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). It is used to get the FXRP Asset Manager address from the Flare Contract Registry. ::: ### Run the Script To run the script, use the following command: ```bash yarn hardhat run scripts/fassets/getFXRP.ts --network coston2 ``` The script will output the FXRP address for the FAssets system on the Coston2 network: ```bash FXRP address 0x0b6A3645c240605887a5532109323A3E12273dc7 ``` :::note Network Addresses The FXRP address will be different depending on which network you are using. ::: ## Using the FXRP Address Once you have the FXRP address, you can: 1. Use it in your smart contracts to interact with FXRP 2. Add it to your wallet to track FXRP balances 3. Use it in DeFi protocols that support FXRP ## Summary In this guide, you learned how to get the FXRP address for the FAssets system by interacting with the `AssetManager` contract using the [`IAssetManager`](/fassets/reference/IAssetManager) interface. :::tip[What's next] To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint). - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## Swap USDT0 to FXRP ## Overview In this guide, you will learn how to swap USDT0 to FXRP using the Uniswap V3 router (SparkDEX). FXRP is the ERC-20 representation of XRP used by [FAssets](/fassets/overview). ## Prerequisites Before starting, make sure you have: - Basic understanding of the [FAssets system](/fassets/overview). - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package installed. - Familiarity with [Uniswap V3 swaps](https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps). ## Required Addresses on Flare Mainnet - V3 SwapRouter (SparkDEX): [0x8a1E35F5c98C4E85B36B7B253222eE17773b2781](https://flare-explorer.flare.network/address/0x8a1E35F5c98C4E85B36B7B253222eE17773b2781?tab=contract). - USDT0: [0xe7cd86e13AC4309349F30B3435a9d337750fC82D](https://flare-explorer.flare.network/address/0xe7cd86e13AC4309349F30B3435a9d337750fC82D). - FXRP: [resolved dynamically from the Asset Manager](/fxrp/token-interactions/fxrp-address). ## Solidity Uniswap V3 Wrapper Contract This Solidity contract wraps the Uniswap V3 router, providing safe token transfers, pool checks, and event logging for debugging. {UniswapV3Wrapper} {/* prettier-ignore */} Open in Remix ### Code Explanation 1. Define the Uniswap V3 router function for swaps, which allows interaction with the Uniswap V3 protocol. It comes from the [Uniswap V3 periphery](https://github.com/Uniswap/v3-periphery). 2. Use the Uniswap V3 factory interface to locate a specific pool for a token pair and the fee tier. 3. Define the Uniswap V3 pool interface that provides liquidity and token details for a given pool. 4. Create the `UniswapV3Wrapper` contract, which acts as a wrapper around Uniswap V3 to simplify swaps and add safety checks. 5. Use the existing Uniswap V3 SwapRouter on Flare (SparkDEX) as the main entry point for executing swaps. 6. Store the Uniswap V3 factory in the contract for looking up pools. 7. Define events that log important actions such as swaps executed, pools checked, and tokens approved. 8. Create a constructor that initializes the swap router and factory. 9. Check if the pool exists and has liquidity, preventing the swaps execution in empty or non-existent pools. 10. Create a swap exact input single function that executes a single-hop swap through the Uniswap V3 router protocol. 10.1. Check if pool exists, ensuring a valid pool address is returned. 10.2. Check if the pool has liquidity, requiring the available liquidity is greater than zero. 10.3. Transfer tokens from the user to this contract, using SafeERC20 for secure transfers. 10.4. Approve the router to spend tokens using the `SafeERC20` library, allowing the router to perform the swap. 10.5. Prepare swap parameters, filling the `ExactInputSingleParams` struct with all necessary details. 10.6. Execute the swap, calling the `swapRouter.exactInputSingle` function to perform the transaction. 10.7. Emit the swap executed event, logging details about the user, tokens, and amounts. ## Execute the Swap with Hardhat To execute a swap, you need to deploy the wrapper contract and call `swapExactInputSingle` with the swap parameters. {UniswapV3WrapperScript} ### Code Breakdown 1. Import contract artifacts, including `IAssetManager`, `UniswapV3Wrapper`, and `ERC20`, which provide access to the deployed contracts. 2. Define Flare Uniswap V3 addresses (SparkDEX), specifying the SwapRouter address used for executing swaps. 3. Set the USDT0 token address on Flare Mainnet, which represents the stablecoin used as the input asset for the swap. 4. Set the pool fee tier to 500 (0.05%), which defines the fee level of the pool to use on the Uniswap V3 router (SparkDEX). 5. Define swap parameters. 6. Define the function `deployAndVerifyContract()` that deploys the UniswapV3Wrapper contract and verifies it on the blockchain explorer. 7. Create the `setupAndInitializeTokens()` function that prepares accounts and ERC20 contracts. 8. Define the `verifyPoolAndLiquidity()` function which ensures the USDT0/FXRP pool exists and is usable. 9. Create the `approveUsdt0ForSwap()` function that approves the wrapper contract to spend USDT0 on behalf of the deployer. 10. Create the `executeSparkDexSwap()` function that executes the swap on SparkDEX using the Uniswap V3 wrapper. 11. Create the `checkFinalBalances()` function that verifies the swap results. 12. Create the `main()` function that orchestrates the entire flow of the swap. ### Run the Script To run the script, you need to execute the following command: ```bash yarn hardhat run scripts/fassets/uniswapV3Wrapper.ts --network flare ``` :::info This script is included in the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit). ::: ## Summary In this guide, you learned how to swap USDT0 to FXRP using the Uniswap V3 router (SparkDEX). :::tip To continue your FAssets development journey, you can: - Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint). - Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem). - Explore [FAssets system settings](/fassets/operational-parameters). ::: --- ## x402 Payment Protocol :::warning[MockUSDT0 Token] This guide currently uses a **MockUSDT0** token for demonstration purposes. FXRP will be supported once it implements the required [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) standard (`transferWithAuthorization`). This guide will be updated when FXRP gains EIP-3009 support. ::: ## Overview The [x402 protocol](https://www.x402.org/) enables HTTP-native payments using the long-reserved HTTP 402 "Payment Required" status code. This guide demonstrates how to implement x402 payments on Flare using [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) for gasless payment settlements. Key benefits of the x402 protocol: - **HTTP-native**: Payments are integrated directly into HTTP request/response flow. - **Gasless for payers**: Users sign authorizations off-chain; servers settle on-chain. - **Front-running protection**: EIP-3009's `receiveWithAuthorization` prevents transaction front-running. - **Idempotent**: Unique nonces ensure each payment can only be processed once. ## Architecture The x402 implementation consists of four components: 1. **MockUSDT0**: ERC-20 token with EIP-3009 support (`transferWithAuthorization`). 2. **X402Facilitator**: Contract that verifies and settles EIP-3009 authorizations. 3. **Server**: Express backend implementing the x402 payment flow. 4. **Agent**: CLI tool for making automated payments. ## EIP-3009 Payment Flow ```mermaid sequenceDiagram participant Agent participant Server participant Facilitator participant MockUSDT0 Agent->>Server: 1. Request Resource Server-->>Agent: 2. 402 + Payment Req Agent->>Agent: 3. Sign EIP-712 Auth(off-chain signature) Agent->>Server: 4. Request + X-Payment header Server->>Facilitator: 5. settlePayment() Facilitator->>MockUSDT0: 6. transferWithAuthorization() Server-->>Agent: 7. 200 OK + Resource ``` The flow works as follows: 1. Client requests a protected resource. 2. Server returns HTTP 402 with payment requirements. 3. Client signs an EIP-712 authorization (off-chain, no gas required). 4. Client resends request with `X-Payment` header containing the signed authorization. 5. Server calls `settlePayment()` on the Facilitator contract. 6. Facilitator executes `transferWithAuthorization` to transfer tokens. 7. Server returns the requested resource with payment confirmation. ## Prerequisites Before starting, ensure you have: - [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) cloned. - C2FLR tokens for gas on Coston2 testnet. - Private key configured in `.env`. ## Step 1: Deploy Contracts Deploy the MockUSDT0 token and X402Facilitator contracts. ```bash yarn hardhat run scripts/x402/deploy.ts --network coston2 ``` The deployment script outputs contract addresses. Add them to your `.env` file: ```env X402_TOKEN_ADDRESS=0x... X402_FACILITATOR_ADDRESS=0x... X402_PAYEE_ADDRESS=0x... ```
View deployment script {x402Deploy}
### Deployment Output ``` ═══════════════════════════════════════════════════════════ x402 Demo Deployment ═══════════════════════════════════════════════════════════ Deployer: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e Balance: 100.0 C2FLR ────────────────────────────────────────────────────────── 📦 Deploying MockUSDT0... MockUSDT0 deployed to: 0x1234... Name: Mock USDT0 Symbol: mUSDT0 Decimals: 6 📦 Deploying X402Facilitator... X402Facilitator deployed to: 0x5678... ⚙️ Configuring facilitator... Added MockUSDT0 as supported token ═══════════════════════════════════════════════════════════ Deployment Summary ═══════════════════════════════════════════════════════════ MockUSDT0: 0x1234... X402Facilitator: 0x5678... Payee Address: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e ``` ## Step 2: Test EIP-3009 Directly Before running the full x402 flow, verify EIP-3009 works correctly: ```bash yarn hardhat run scripts/x402/testEip3009.ts --network coston2 ``` This script: 1. Mints test tokens if needed. 2. Creates an EIP-712 signed authorization. 3. Executes `transferWithAuthorization`. 4. Verifies nonce replay protection. ## Step 3: Start the Server The server implements the x402 payment middleware. ```bash npx ts-node scripts/x402/server.ts ``` The server runs on `http://localhost:3402` and exposes: | Endpoint | Price | Description | | ----------------------- | --------- | --------------- | | `GET /api/public` | Free | Public data | | `GET /api/premium-data` | 0.1 USDT0 | Premium data | | `GET /api/report` | 0.5 USDT0 | Detailed report | | `GET /health` | Free | Health check |
View server implementation {x402Server}
### Server Code Breakdown 1. **Configuration**: Load environment variables for token, facilitator, and payee addresses. 2. **Payment Requirements**: Define resources that require payment with their prices. 3. **x402 Middleware**: Intercept requests to protected endpoints. - If no `X-Payment` header, return 402 with payment requirements. - If payment header present, verify and settle the payment. 4. **Payment Verification**: Use the Facilitator contract to verify the EIP-3009 authorization. 5. **Payment Settlement**: Call `settlePayment()` to execute the token transfer. 6. **Response Headers**: Include `X-Payment-Response` with transaction details. ## Step 4: Run the Agent The agent is an interactive CLI for making x402 payments. ```bash npx ts-node scripts/x402/agent.ts ```
View agent implementation {x402Agent}
### Agent Commands ``` 📋 Available Commands: 1. Fetch /api/public (free) 2. Fetch /api/premium-data (0.1 USDT0) 3. Fetch /api/report (0.5 USDT0) 4. Check balance 5. Mint test tokens 6. Exit ``` ### Example Payment Flow ``` 🔍 Checking payment requirements for /api/premium-data... 💰 Payment Required: Amount: 0.1 USDT0 Payee: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e 💳 Your Balance: 1000.0 USDT0 Do you want to authorize payment of 0.1 USDT0? (y/n): y ✍️ Creating EIP-3009 authorization... 📝 EIP-712 Authorization Details: ────────────────────────────────────────────────── From: 0xYourAddress... To: 0xPayeeAddress... Value: 0.1 USDT0 Valid After: 2024-01-15T10:00:00.000Z Valid Before: 2024-01-15T10:05:00.000Z Nonce: 0x1234... ────────────────────────────────────────────────── ✅ Authorization signed 🔐 Verifying authorization with facilitator... Payment ID: 0xabcd... Valid: true 📤 Submitting payment and fetching resource... ✅ Payment successful! ────────────────────────────────────────────────── 📦 Resource Data: { "message": "Premium data accessed successfully!", "data": { "flarePrice": 0.0234, "timestamp": 1705312800000, "secret": "This is premium content only available after payment" } } 🧾 Payment Receipt: Transaction: 0xdef456... Payment ID: 0xabcd... ``` ## x402 Protocol Details ### 402 Response Format When a resource requires payment, the server returns: ```json { "error": "Payment Required", "x402Version": "1", "accepts": [ { "scheme": "exact", "network": "flare-coston2", "maxAmountRequired": "100000", "payTo": "0x...", "asset": "USDT0", "extra": { "tokenAddress": "0x...", "facilitatorAddress": "0x...", "chainId": 114 } } ] } ``` ### X-Payment Header The client sends payment authorization as a Base64-encoded JSON: ```json { "from": "0x...", "to": "0x...", "token": "0x...", "value": "100000", "validAfter": "1704067200", "validBefore": "1704070800", "nonce": "0x...", "v": 28, "r": "0x...", "s": "0x..." } ``` ### X-Payment-Response Header The server confirms settlement with: ```json { "paymentId": "0x...", "transactionHash": "0x...", "settled": true } ``` ## EIP-712 Signature The agent signs authorizations using EIP-712 typed data: ```typescript const domain = { name: "Mock USDT0", version: "1", chainId: 114, verifyingContract: tokenAddress, }; const types = { TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }, ], }; const message = { from: userAddress, to: payeeAddress, value: amount, validAfter: Math.floor(Date.now() / 1000) - 60, validBefore: Math.floor(Date.now() / 1000) + 300, nonce: ethers.hexlify(ethers.randomBytes(32)), }; const signature = await wallet.signTypedData(domain, types, message); ``` ## Security Considerations 1. **Front-running Protection**: Use `receiveWithAuthorization` when the payee is a contract to prevent front-running attacks. 2. **Nonce Management**: Always use cryptographically random 32-byte nonces. Never reuse nonces. 3. **Time Bounds**: Set reasonable `validAfter` and `validBefore` windows. The example uses a 5-minute window. 4. **Domain Verification**: Always verify the contract address in the EIP-712 domain matches the expected token. ## Troubleshooting **Error: X402_TOKEN_ADDRESS not set** - Solution: Deploy contracts first and add addresses to `.env`. **Error: Payment verification failed** - Solution: Ensure the authorization hasn't expired (`validBefore`). - Check that the nonce hasn't been used before. **Error: Insufficient balance** - Solution: Mint test tokens using the agent's option 5. ## References - [x402 Protocol Specification](https://www.x402.org/) - [EIP-3009: Transfer With Authorization](https://eips.ethereum.org/EIPS/eip-3009) - [Coinbase x402 Implementation](https://github.com/coinbase/x402) - [EIP-712: Typed Structured Data Hashing](https://eips.ethereum.org/EIPS/eip-712) :::tip[Next Steps] To continue your development journey: - Learn about [FAssets minting](/fassets/developer-guides/fassets-mint). - Explore [swapping tokens to FXRP](/fxrp/token-interactions/usdt0-fxrp-swap). - Read about [Flare's data protocols](/network/overview). ::: --- ## Gasless FXRP Payments This guide explains how to set up gasless FXRP (FAsset) transfers on Flare. Users sign payment requests off-chain with [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data, and a relayer submits them on-chain and pays gas on their behalf. ### Standards explained [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) (Transfer with Authorization) extends [ERC-20](https://eips.ethereum.org/EIPS/eip-20) with meta-transactions. The token holder signs an authorization off-chain, and a relayer executes the transfer on-chain and pays gas. This guide uses a custom [EIP-712](https://eips.ethereum.org/EIPS/eip-712) payment message and a forwarder contract instead of EIP-3009 on the token itself. The forwarder pulls FXRP from the user (after a one-time approval) and sends it to the provided recipient. The relayer pays gas on behalf of the user. ## Architecture The architecture of the gasless FXRP payments system is as follows: ```mermaid sequenceDiagram participant User participant Relayer participant GaslessPaymentForwarder participant FXRP User->>User: 1. Sign payment (EIP-712)off-chain, no gas User->>Relayer: 2. POST signed request Relayer->>GaslessPaymentForwarder: 3. executePayment()(pays gas) GaslessPaymentForwarder->>FXRP: 4. FXRP transfer FXRP-->>GaslessPaymentForwarder: 5. Transfer confirmed GaslessPaymentForwarder-->>Relayer: 6. Execution complete Relayer-->>User: 7. Payment confirmed ``` ## Project setup To follow this guide, create a new [Hardhat](https://hardhat.org/) project or use the [Flare Hardhat Starter](/network/guides/hardhat-foundry-starter-kit) kit. You will need to install the following dependencies: ```bash npm install ethers viem express cors dotenv @openzeppelin/contracts \ @flarenetwork/flare-periphery-contracts ``` Add the following files to your project: ### Gasless Payment Forwarder Contract Save as `contracts/GaslessPaymentForwarder.sol` to create the forwarder smart contract that will be used to execute gasless payments.
View `contracts/GaslessPaymentForwarder.sol` {GaslessPaymentForwarder} **How the contract works** 1. Define libraries and state (nonces, authorizedRelayers). 2. Define events (PaymentExecuted, RelayerAuthorized). 3. Define errors. 4. Implement a constructor that sets the EIP-712 domain and owner. 5. Implement `fxrp` to return the FXRP token from the Flare Contract Registry. 6. Define `executePayment(from, to, amount, deadline, signature)`. 7. Hash the request (type hash + from, to, amount, nonce, deadline) and get the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) digest. 8. Recover the signer from the signature. 9. Require signer to be the from address; increment nonce. 10. Require sufficient allowance; transfer amount from sender to recipient. 11. Define view functions: getNonce, getDomainSeparator, getPaymentRequestHash. 12. Define setRelayerAuthorization for the relayer allowlist.
Run the following command to compile the contract and generate the typechain-types: ```bash yarn hardhat compile ``` It will generate the `typechain-types` directory with the contract interfaces and factories. ### Utilities Save as `utils/payment.ts` to create the utility functions that will be used to create and sign payment requests off-chain. :::info This utility uses the `typechain-types` generated by the previous command. :::
View `utils/payment.ts` {PaymentUtils} **Code Breakdown** 1. Import the necessary libraries. 2. Define constants and EIP-712 types (PaymentRequest: from, to, amount, nonce, deadline). 3. Define types for sign params, payment request, approval result, and user status. 4. Parse human-readable amount to raw bigint with decimals. 5. Format the raw amount to a readable string with decimals. 6. Get FXRP decimals from the forwarder and token contract. 7. Get the current nonce for a user from the forwarder. 8. Sign the payment message with EIP-712 and return the signature. 9. Create a full payment request: fetch nonce, set deadline, sign, return payload for the relayer. 10. Approve the forwarder to spend FXRP (one-time). 11. Check user balance, allowance, nonce, and whether approval is needed.
### Relayer Service This is a relayer service that submits payment requests to the Flare blockchain and pays gas on behalf of the user. Save as `relayer/index.ts` to start the relayer service.
View `relayer/index.ts` {Relayer} **Code Breakdown** 1. Import necessary libraries. 2. Define EIP-712 domain and payment request types (from, to, amount, nonce, deadline); define NETWORKS (flare, coston2, songbird). 3. Define RelayerConfig, PaymentRequest, and ExecuteResult types. 4. Implement GaslessRelayer class: constructor sets provider, wallet, and forwarder. 5. Execute payment: normalize and validate request, recover signer with EIP-712, validate request (balance, allowance, deadline), staticCall, recheck nonce, estimate gas, call executePayment, wait for receipt, return result. 6. Validate request: check deadline against chain time, check sender balance and allowance. 7. Get nonce for an address from the forwarder. 8. Get FXRP token decimals from the forwarder and token contract. 9. Get the relayer FLR balance for gas. 10. Start Express server: GET /nonce/:addr, POST /execute. 11. Main: read env (RELAYER_PRIVATE_KEY, FORWARDER_ADDRESS, RPC_URL, PORT), create relayer, log FLR balance, start server. 12. Run main to start the relayer service.
### Deploy Script Save the snippet to `scripts/gaslessForwarder/deploy.ts` to deploy the [`GaslessPaymentForwarder`](/fxrp/token-interactions/gasless-fxrp-payments#gasless-payment-forwarder-contract) contract.
View scripts/gaslessForwarder/deploy.ts {DeployScript} **Code Breakdown** 1. Import Hardhat and TypeChain types. 2. Get deployer signer and balance; log address and FLR balance. 3. Deploy GaslessPaymentForwarder (no constructor args; FXRP from Flare Contract Registry); wait for deployment and get address. 4. Get FXRP token address from the forwarder; log deployment summary (network, contract, FXRP, owner). 5. Verify contract on block explorer (constructor arguments empty). 6. Run main: exit 0 on success, exit 1 on error.
### Example Flow Use `scripts/example-usage.ts` to run the example flow.
View scripts/example-usage.ts {ExampleUsage} **Code Breakdown** 1. Import the necessary libraries. 2. Read configuration from the environment like the RPC URL, relayer URL, forwarder address, and user private key. 3. Validate the configuration to ensure that the forwarder address and user private key are set. 4. Create a provider and a wallet. 5. Check user FXRP balance and allowance. 6. Approve the forwarder to spend FXRP. 7. Create a payment request and sign it. 8. Submit the payment request to the relayer. 9. Call the main function to run the example flow.
## Configuration Create a `.env` file in your project root with the following variables: | Variable | Description | | --------------------- | --------------------------------------------------- | | `PRIVATE_KEY` | Deployer private key (for deployment) | | `RELAYER_PRIVATE_KEY` | Relayer wallet (pays gas) | | `USER_PRIVATE_KEY` | User wallet private key for testing | | `FORWARDER_ADDRESS` | Deployed contract address (set after deploy) | | `RPC_URL` | Flare network RPC | | `RELAYER_URL` | Relayer HTTP URL (default: `http://localhost:3000`) | ## Deploy and run ### Compile Contracts Run the following command to compile the contracts: ```bash yarn hardhat compile ``` ### Deploy the Forwarder Deploy the [`GaslessPaymentForwarder`](/fxrp/token-interactions/gasless-fxrp-payments#gasless-payment-forwarder-contract) contract to Coston2 (testnet): ```bash yarn hardhat run scripts/gaslessForwarder/deploy.ts --network coston2 ``` Set `FORWARDER_ADDRESS` in `.env` to the deployed contract address. ### Start the Relayer Run the following command to start the relayer service. The relayer is an Express server that submits payment requests on the Flare blockchain and pays gas on behalf of the user. ```bash npx ts-node relayer/index.ts ``` **Main relayer backend endpoints:** | Method | Path | Description | | ------ | ----------------- | ---------------------- | | POST | `/execute` | Execute single payment | | GET | `/nonce/:address` | Get nonce for address | ### Run the example Flow Run the following command to run the example: ```bash npx ts-node scripts/example-usage.ts ``` This script will execute the following steps: 1. Checks the user's balance and allowance. 2. Approves the forwarder to spend FXRP if needed. 3. Creates a payment request. 4. Submits the payment request to the relayer. 5. Checks the payment status. You should see the following output:
View example output ```console === FXRP Gasless Payment Example === User address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Forwarder: 0xffc5F792e1Ca050B598577fFaFa30634A66174A5 Relayer: http://localhost:3000 Step 1: Checking FXRP balance and allowance... FXRP Token: 0x0b6A3645c240605887a5532109323A3E12273dc7 Balance: 11773.287907 FXRP Allowance: 0.0 FXRP Nonce: 0 Step 2: Approving FXRP for gasless payments... Approved! TX: 0x4aa5304c4e3e261d9d88ac1fe24a9d96faaa5786065a9253de463a5c33d0c368 Step 3: Creating payment request... To: 0x5EEeaD99dFfeB32Fe96baad6554f6E009c8B348a Amount: 0.1 FXRP Signed request created: From: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 To: 0x5EEeaD99dFfeB32Fe96baad6554f6E009c8B348a Amount: 0.1 FXRP Deadline: 2026-02-13T14:14:20.000Z Signature: 0x0cc5532b3c67eb0ef4... Step 4: Submitting to relayer... Payment executed successfully! Transaction: 0xd18b532b294656fbeadd4884b6baf5e45efabb3809787c1625f675cccff2b648 Block: 27196879 Gas used: 229589 ```
:::tip[Next steps] - [Get FXRP token address](/fxrp/token-interactions/fxrp-address) for use in your app. - [Swap USDT0 to FXRP](/fxrp/token-interactions/usdt0-fxrp-swap) to acquire FXRP. - Review [FXRP operational parameters](/fxrp/parameters). - [Gasless USD₮0 Transfers](/network/guides/gasless-usdt0-transfers) — Similar pattern for USDT0 on Flare. ::: --- ## Get Upshift Vault Status This guide demonstrates how to retrieve information about an Upshift vault, including vault configuration, LP token info, user balances, and withdrawal epoch information. The Upshift vault is an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style tokenized vault compatible with [FAssets](/fassets/overview). ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) ## Upshift Vault Status Script The following script retrieves and displays information about the Upshift vault: {UpshiftStatus} ## Script Breakdown The `main()` function executes the following steps: 1. **Initialize:** Gets the user account and logs the vault and user addresses. 2. **Connect to vault:** Creates an instance of the Upshift vault contract. 3. **Get reference asset info:** Retrieves the vault's reference asset (FXRP) address, symbol, and decimals. 4. **Get LP token info:** Retrieves the vault's LP token address, which represents user shares. 5. **Get vault configuration:** Fetches key vault parameters. 6. **Get withdrawal epoch info:** Retrieves the current withdrawal epoch (year, month, day) and claimable epoch. 7. **Get user balances:** Shows the user's reference asset balance and LP token balance. 8. **Check allowances:** Displays the user's token allowances to the vault for both the reference asset and LP token. ## Running the Script To run the Upshift vault status script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. 3. Run the script using Hardhat: ```bash yarn hardhat run scripts/upshift/status.ts --network coston2 ``` ## Output The script outputs the following information about the Upshift vault: ```bash VAULT STATUS Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81 User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Reference Asset Address: 0x0b6A3645c240605887a5532109323A3E12273dc7 Symbol: FTestXRP Decimals: 6 LP Token Address: 0x1234567890abcdef1234567890abcdef12345678 Vault Configuration Withdrawals Paused: false Lag Duration: 86400 seconds Withdrawal Fee: 0.5% Instant Redemption Fee: 1.0% Max Withdrawal Amount: 1000000.0 FTestXRP Withdrawal Epoch Year: 2025, Month: 1, Day: 15 Claimable Epoch: 14 User Balances FTestXRP Balance: 100.0 LP Token Balance: 95.5 Allowances FTestXRP Allowance to Vault: 0.0 LP Token Allowance to Vault: 0.0 ``` ## Summary In this guide, you learned how to retrieve status information from an Upshift vault, including vault configuration, LP token details, user balances, and withdrawal epoch information. :::tip[What's next] To continue your Upshift development journey, you can: - Learn more about [FAssets](/fassets/overview) and how the system works. - Learn how to [deposit assets into an Upshift vault](/fxrp/upshift/deposit). - Learn how to [instantly redeem from an Upshift vault](/fxrp/upshift/instant-redeem) for immediate liquidity. - Learn how to [request a redemption from an Upshift vault](/fxrp/upshift/request-redeem) for delayed liquidity. - Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. ::: --- ## Deposit Assets into Upshift Vault This guide demonstrates how to deposit assets into an Upshift vault. The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style tokenized vault, allowing users to deposit assets (e.g., FXRP) and receive LP tokens (vault shares) in return. Depositing assets is the process of transferring them to the vault and receiving LP tokens that represent your proportional ownership of the vault's assets. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - Sufficient asset balance (e.g., FXRP) to deposit into the vault. ## Upshift Vault Deposit Script The following script demonstrates how to deposit assets into the Upshift vault: {UpshiftDeposit} ## Script Breakdown The `main()` function executes the following steps: 1. **Initialize:** Gets the user account and logs the vault and user addresses. 2. **Connect to vault:** Creates an instance of the Upshift vault contract. 3. **Get reference asset info:** Retrieves the vault's reference asset (FXRP) address, symbol, and decimals. 4. **Calculate deposit amount:** Converts the desired amount into the correct units based on decimals. 5. **Check balance:** Verifies the user has sufficient balance to cover the deposit. 6. **Approve allowance:** Approves the vault to spend the deposit amount if needed. 7. **Preview deposit:** Calls [`previewDeposit()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-7.-previewdeposit-uint256-assets) to see the expected shares and amount in reference tokens. 8. **Get LP token info:** Records the LP token balance before the deposit. 9. **Execute deposit:** Calls [`deposit()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-1.-deposit-uint256-assets-address-receiver) to transfer assets and mint LP tokens. 10. **Verify deposit:** Confirms the deposit by checking the new LP token balance and shares received. ## Running the Script To run the Upshift vault deposit script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. 3. Adjust the `DEPOSIT_AMOUNT` constant to the desired number of tokens. 4. Ensure your account has sufficient asset balance (e.g., FXRP) to cover the deposit. 5. Run the script using Hardhat: ```bash yarn hardhat run scripts/upshift/deposit.ts --network coston2 ``` ## Output The script outputs the following information about the deposit: ```bash DEPOSIT TO VAULT Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81 User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Reference Asset (asset depositing): 0x0b6A3645c240605887a5532109323A3E12273dc7 Deposit Amount: 1 FTestXRP (1000000) Balance: 100.0 FTestXRP Current Allowance: 0.0 FTestXRP Approving vault to spend 1 FTestXRP tokens Approval Tx: 0x87a36c1009575719fd3adb9a1bb2e3062a601bf910fc7fac3248da71891c39a4 Expected Shares: 0.99 Amount in Reference Tokens: 1.0 LP Balance Before: 0.0 Deposit: (tx: 0x446b7a171859d676677fc870cff81c7e8c0d618fc3588e60665792da86b94c50 , block: 12345678 ) Verifying deposit... LP Balance After: 0.99 Shares Received: 0.99 ``` ## Summary In this guide, you learned how to deposit assets into an Upshift vault by specifying the amount of assets to deposit and receiving LP tokens in return. :::tip[What's next] To continue your Upshift development journey, you can: - Learn more about [FAssets](/fassets/overview) and how the system works. - Learn how to [get Upshift vault status](/fxrp/upshift/status) to monitor your position. - Learn how to [instantly redeem from an Upshift vault](/fxrp/upshift/instant-redeem) for immediate liquidity. - Learn how to [request a redemption from an Upshift vault](/fxrp/upshift/request-redeem) for delayed liquidity. - Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. ::: --- ## Instant Redeem from Upshift Vault This guide demonstrates how to perform an instant redemption from an Upshift vault. The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style tokenized vault that supports instant redemptions for immediate liquidity when available. Instant redemption burns your LP tokens (vault shares) and immediately returns the underlying assets to your wallet, subject to an instant redemption fee. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - LP tokens (vault shares) in the Upshift vault to redeem. ## Upshift Vault Instant Redeem Script The following script demonstrates how to perform an instant redemption from the Upshift vault: {UpshiftInstantRedeem} ## Script Breakdown The `main()` function executes the following steps: 1. **Initialize:** Gets the user account and logs the vault and user addresses. 2. **Connect to vault:** Creates an instance of the Upshift vault contract. 3. **Get reference asset info:** Retrieves the vault's reference asset (FXRP) address, symbol, and decimals. 4. **Get LP token info:** Retrieves the LP token address and the user's current LP balance. 5. **Validate balance:** Converts the shares amount and checks if the user has sufficient LP tokens. 6. **Preview redemption:** Calls [`previewRedemption()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) to see the expected assets before and after the instant redemption fee. 7. **Get asset balance:** Records the user's asset balance before redemption. 8. **Execute instant redemption:** Calls [`instantRedeem()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-4.-instantredeem-uint256-shares-address-receiveraddr-address-holderaddr) to burn LP tokens and receive assets immediately. 9. **Verify redemption:** Confirms the redemption by checking the updated LP and asset balances. ## Understanding Instant Redemption The Upshift vault supports two types of redemptions: - **Instant Redemption**: Immediately burns LP tokens and returns assets, but incurs an instant redemption fee. - **Requested Redemption**: Creates a withdrawal request that is processed after a lag period, with a lower fee. The instant redemption fee compensates the vault for providing immediate liquidity. The [`previewRedemption()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) function shows both the gross and net amounts you'll receive. ## Running the Script To run the Upshift vault instant redeem script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. 3. Adjust the `SHARES_TO_REDEEM` constant to the desired number of shares. 4. Ensure your account has sufficient LP tokens (vault shares) to cover the redemption. 5. Run the script using Hardhat: ```bash yarn hardhat run scripts/upshift/instantRedeem.ts --network coston2 ``` ## Output The script outputs the following information about the instant redemption: ```bash INSTANT REDEEM FROM VAULT Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81 User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Reference Asset (asset receiving): 0x0b6A3645c240605887a5532109323A3E12273dc7 LP Balance: 10.0 Shares to Redeem: 1 (1000000) Instant Redemption Fee: 1.0% Expected Assets (before fee): 1.0 FTestXRP Expected Assets (after fee): 0.99 FTestXRP Asset Balance Before: 50.0 FTestXRP Instant Redeem: (tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 , block: 12345678 ) Verifying redemption... LP Balance After: 9.0 Shares Redeemed: 1.0 Assets Received: 0.99 FTestXRP ``` ## Summary In this guide, you learned how to perform an instant redemption from an Upshift vault by specifying the number of LP tokens (shares) to redeem. The instant redemption provides immediate liquidity but incurs a fee. :::tip[What's next] To continue your Upshift development journey, you can: - Learn more about [FAssets](/fassets/overview) and how the system works. - Learn how to [deposit assets into an Upshift vault](/fxrp/upshift/deposit). - Learn how to [request a redemption from an Upshift vault](/fxrp/upshift/request-redeem) for delayed liquidity. - Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. ::: --- ## Request Redeem from Upshift Vault This guide demonstrates how to request a delayed redemption from an Upshift vault. The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style tokenized vault that supports requested redemptions with a lower fee than instant redemptions. Requesting a redemption locks your LP tokens (vault shares) and creates a withdrawal request. After the lag duration passes, you can claim your assets using the [claim redemption script](/fxrp/upshift/claim). ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - LP tokens (vault shares) in the Upshift vault to redeem. ## Upshift Vault Request Redeem Script The following script demonstrates how to request a delayed redemption from the Upshift vault: {UpshiftRequestRedeem} ## Script Breakdown The `main()` function executes the following steps: 1. **Initialize:** Gets the user account and logs the vault and user addresses. 2. **Connect to vault:** Creates an instance of the Upshift vault contract. 3. **Get reference asset info:** Retrieves the vault's reference asset (FXRP) address, symbol, and decimals. 4. **Get LP token info:** Retrieves the LP token address and the user's current LP balance. 5. **Validate balance:** Converts the shares amount and checks if the user has sufficient LP tokens. 6. **Approve LP allowance:** Approves the vault to spend LP tokens if the current allowance is insufficient. 7. **Check vault configuration:** Verifies withdrawals are not paused and the amount doesn't exceed maximum limits. 8. **Preview redemption:** Calls [`previewRedemption()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) to see expected assets before and after the withdrawal fee. 9. **Print withdrawal epoch:** Displays the current epoch and claimable epoch information. 10. **Execute request redeem:** Calls [`requestRedeem()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-2.-requestredeem-uint256-shares-address-receiveraddr-address-holderaddr) to lock LP tokens and create a withdrawal request. 11. **Verify request:** Confirms the request by checking the updated LP balance and showing when assets can be claimed. ## Understanding Requested Redemption The Upshift vault supports two types of redemptions: - **Instant Redemption**: Immediately burns LP tokens and returns assets, but incurs an instant redemption fee. - **Requested Redemption**: Creates a withdrawal request that is processed after a lag period, with a lower fee. The requested redemption process works as follows: 1. **Request**: Call [`requestRedeem()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-2.-requestredeem-uint256-shares-address-receiveraddr-address-holderaddr) to lock your LP tokens and create a withdrawal request for the current epoch. 2. **Wait**: The lag duration must pass before you can claim your assets. 3. **Claim**: After the lag period execute the [claim redemption script](/fxrp/upshift/claim) to receive your assets. This delayed mechanism allows the vault to manage liquidity more efficiently while offering users a lower fee option. ## Running the Script To run the Upshift vault request redeem script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. 3. Adjust the `SHARES_TO_REDEEM` constant to the desired number of shares. 4. Ensure your account has sufficient LP tokens (vault shares) to cover the redemption. 5. Run the script using Hardhat: ```bash yarn hardhat run scripts/upshift/requestRedeem.ts --network coston2 ``` ## Output The script outputs the following information about the request redemption: ```bash REQUEST REDEEM FROM VAULT Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81 User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Reference Asset (asset receiving): 0x0b6A3645c240605887a5532109323A3E12273dc7 LP Balance: 10.0 Shares to Redeem: 1 (1000000) Current LP Allowance: 0.0 Approving vault to spend 1 LP tokens... Approval Tx: 0x87a36c1009575719fd3adb9a1bb2e3062a601bf910fc7fac3248da71891c39a4 Lag Duration: 86400 seconds Withdrawal Fee: 0.5% Withdrawals Paused: false Max Withdrawal Amount: 1000000.0 FTestXRP Expected Assets (before fee): 1.0 FTestXRP Expected Assets (after fee): 0.995 FTestXRP Current Epoch - Year: 2025, Month: 1, Day: 15 Claimable Epoch: 14 Request Redeem: (tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 , block: 12345678 ) LP Balance After: 9.0 Shares Locked: 1.0 Claim your assets after: 2025/1/16 ``` ## Summary In this guide, you learned how to request a delayed redemption from an Upshift vault by specifying the number of LP tokens (shares) to redeem. The requested redemption has a lower fee than instant redemption but requires waiting for the lag duration before claiming your assets. :::tip[What's next] To continue your Upshift development journey, you can: - Learn how to [get Upshift vault status](/fxrp/upshift/status) to monitor your redemption requests. - Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. - Learn how to [instantly redeem from an Upshift vault](/fxrp/upshift/instant-redeem) for immediate liquidity. - Learn how to [deposit assets into an Upshift vault](/fxrp/upshift/deposit). - Learn more about [FAssets](/fassets/overview) and how the system works. ::: --- ## Claim Redemption from Upshift Vault This guide demonstrates how to claim assets from a previously requested redemption in an Upshift vault. The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style tokenized vault that supports requested redemptions with a lower fee than instant redemptions. Claiming is the final step after you have [requested a redemption](/fxrp/upshift/request-redeem). Once the lag duration has passed, you call [`claim()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-3.-claim-uint256-year-uint256-month-uint256-day-address-receiveraddr) to receive your underlying assets (e.g., FXRP) in your wallet. ## Prerequisites - [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) - [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) - Understanding of [FAssets](/fassets/overview) - A pending redemption request from a previous [request redeem](/fxrp/upshift/request-redeem) call. ## Upshift Vault Claim Script The following script demonstrates how to claim assets from the Upshift vault: {UpshiftClaim} ## Script Breakdown The `main()` function executes the following steps: 1. **Initialize:** Gets the user account and logs the vault, user, receiver addresses, and redemption date. 2. **Connect to vault:** Creates an instance of the Upshift vault contract. 3. **Get reference asset info:** Retrieves the vault's reference asset (FXRP) address, symbol, and decimals. 4. **Check pending redemption:** Verifies that there are shares to claim for the given date and receiver. 5. **Get LP token info:** Retrieves the LP token address, decimals, and symbol. 6. **Preview redemption:** Calls [`previewRedemption()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) to see expected assets before and after the withdrawal fee. 7. **Check if claimable:** Ensures the current block timestamp is past the claimable date (plus a small buffer) before proceeding. 8. **Get balance before:** Records the receiver's asset balance before the claim. 9. **Execute claim:** Calls [`claim()`](https://docs.upshift.finance/developer-docs/vault-contract-interface?q=getBurnableAmountByReceiver#id-3.-claim-uint256-year-uint256-month-uint256-day-address-receiveraddr) with the redemption date and receiver to transfer assets. 10. **Verify claim:** Confirms the claim by comparing the receiver's new asset balance to the expected amount. ## Understanding the Claim Process The claim step completes the requested redemption flow: 1. **Request:** You previously called [`requestRedeem()`](/fxrp/upshift/request-redeem) to lock LP tokens and create a withdrawal request for a specific epoch. 2. **Wait:** The lag duration must pass before assets can be claimed. 3. **Claim:** Once the claimable date has been reached, call `claim(year, month, day, receiver)` to receive your assets. The script uses the **redemption date** (year, month, day) that was output when you ran the request redeem script. You must set `YEAR`, `MONTH`, and `DAY` in the script to match that date. ## Running the Script To run the Upshift vault claim script: 1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. 2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. 3. Set `YEAR`, `MONTH`, and `DAY` to the claimable date from your [request redeem](/fxrp/upshift/request-redeem) output. 4. Optionally set `RECEIVER_ADDRESS` to claim to a different address (leave empty to use the signer). 5. Ensure the claimable date has passed (the script checks and exits with a message if not). 6. Run the script using Hardhat: ```bash yarn hardhat run scripts/upshift/claim.ts --network coston2 ``` ## Output The script outputs the following information about the claim: ```bash CLAIM REDEMPTION Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81 User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Receiver Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 Redemption Date: 2026-01-23 1. Checking pending redemption... 2. Previewing redemption... Assets (before fee): 1.0 FTestXRP Assets (after fee): 0.995 FTestXRP Fee: 0.005 FTestXRP Shares to claim: 1.0 LP Token Address: 0x... 3. Checking if claimable... Current Timestamp: 1737648000 Claimable Epoch: 1737586800 Ready to claim! 4. Checking receiver balance before... Balance: 50.0 FTestXRP 5. Claiming... Claim: (tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 , block: 12345678 ) 6. Verifying claim... Balance After: 50.995 FTestXRP Received: 0.995 FTestXRP Claim successful! ``` ## Summary In this guide, you learned how to claim assets from an Upshift vault after a requested redemption. You use the redemption date (year, month, day) from your request redeem output, and once the lag period has passed, the claim script transfers the underlying assets to your wallet. :::tip[What's next] To continue your Upshift development journey, you can: - Learn how to [get Upshift vault status](/fxrp/upshift/status) to monitor your redemption requests. - Learn how to [request a redemption](/fxrp/upshift/request-redeem) from an Upshift vault. - Learn how to [instantly redeem](/fxrp/upshift/instant-redeem) from an Upshift vault for immediate liquidity. - Learn how to [deposit assets](/fxrp/upshift/deposit) into an Upshift vault. - Learn more about [FAssets](/fassets/overview) and how the system works. ::: --- ## Upshift Vaults [Upshift](https://upshift.finance) is a yield-generating protocol that supports FXRP through strategy-driven vaults on Flare. It provides [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style vaults that allow users to deposit FXRP and earn yield generated from on-chain DeFi strategies, while abstracting away strategy execution and risk management. Upshift vaults support both instant and requested redemptions, depending on available liquidity. Users receive vault shares that represent their proportional ownership of the vault's assets, with yield accruing as share value increases over time. The [Upshift vault](https://app.upshift.finance/pools/14/0x373D7d201C8134D4a2f7b5c63560da217e3dEA28) is part of the XRP finance stack on Flare Network. The example scripts in the guides below interact with the Coston2 testnet [vault](https://coston2-explorer.flare.network/address/0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81). It inherits the transparency model of the [FAssets](/fassets/overview) system: minting and redemption are proof-backed, and vault interactions are recorded on-chain. - **Funds are on-chain:** User deposits, vault balances, withdrawal requests, and claims are executed as smart contract state changes on Flare. You can verify all vault holdings on [DeBank](https://debank.com/profile/0xEDb7B1e92B8D3621b46843AD024949F10438374B) portfolio tracker. - **User ownership is verifiable:** A user's position is represented directly on-chain and can be queried from smart contracts. - **Instruction flow is auditable:** XRPL-triggered actions are executed on Flare via [Smart Accounts](/smart-accounts/overview) with [Flare Data Connector](/fdc/overview) backed verification and onchain execution. You can verify all smart account activity on the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/smart-accounts). ## How Upshift Vaults Work This diagram shows the flow of operations for a user interacting with an Upshift vault. ```mermaid flowchart TB UserFXRP[("User's FXRP")] Deposit[deposit] LP[("LP Tokens (vault shares)")] UserFXRP --> Deposit --> LP LP --> InstantRedeem[instantRedeem] InstantRedeem -->|"instant redemption fee applies"| InstantFXRP[("FXRP to user")] LP --> RequestRedeem[requestRedeem] RequestRedeem --> Epoch["Withdrawal request(epoch: year, month, day)"] Epoch --> Wait["Wait lag duration"] Wait --> Claim[claim] Claim -->|"withdrawal fee applies"| RequestedFXRP[("FXRP to user")] ``` The flow of operations for a user interacting with an Upshift vault is as follows: - **Deposit:** Transfer FXRP to the vault and receive the corresponding LP tokens (shares). Yield accrues as share value increases. - **Instant redeem:** Burn the corresponding LP tokens and receive FXRP immediately via the `instantRedeem()` function. In this case you need to pay the instant redemption fee. Use this option when you need liquidity right away. - **Requested redeem:** Lock the LP tokens with `requestRedeem()` and create a withdrawal request for the current epoch. After the lag duration, call `claim(year, month, day, receiver)` to receive FXRP. Lower fee than instant redeem. Use this option when you can wait. :::tip[What's next] - Learn more about [FAssets](/fassets/overview) and how the system works. - Explore how to [mint FXRP](/fassets/developer-guides/fassets-mint) from XRP. - Discover how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) back to XRP. ::: --- ## Flare Smart Accounts The Flare Smart Accounts is an account abstraction that allows XRPL users to perform actions on the Flare chain without owning any FLR token. Each XRPL address is assigned a unique **smart account** on the Flare chain, which it can control alone. They do so through `Payment` transactions on the XRPL. The Flare Smart Accounts are especially useful for interacting with the FAssets workflow. ## Workflow Flare Smart Accounts support two complementary flows for turning an XRPL `Payment` into actions on Flare. ### Proof-based flow (payment reference) 1. The XRPL user sends a `Payment` transaction to the operator's XRPL address with a 32-byte instruction encoded as the **payment reference**. 2. The operator requests a [`Payment` attestation](/fdc/attestation-types/payment) from the [Flare Data Connector](/fdc/overview) and submits it to the `MasterAccountController`. 3. The XRPL user's `PersonalAccount` performs the action encoded in the reference. Proof-based flow: XRPL `Payment` (1) → operator backend (2) → FDC attestation (3, 4) → `MasterAccountController` (5) → `PersonalAccount` (6). ### Direct-minting (memo) flow 1. The XRPL user sends a `Payment` to an FAssets agent's XRPL address that mints FXRP directly to the smart account, with the [memo field](/smart-accounts/custom-instruction) carrying the instruction. 2. The FAssets `AssetManager` mints FXRP to the `MasterAccountController` and calls back into [`mintedFAssets`](/smart-accounts/reference/IMasterAccountController#mintedfassets) on `MemoInstructionsFacet`. 3. The facet routes the FAssets to the user's `PersonalAccount`, optionally pays an executor fee, and dispatches any memo instruction (`0xFF` execute UserOp, `0xE0` ignore memo, `0xE1` set nonce, `0xE2` replace fee, `0xD0`/`0xD1` set/remove executor). ```mermaid graph TB subgraph XRPL["XRPL"] User(["user account"]) Agent(["FAssets agentXRPL address"]) end subgraph Flare["Flare"] Executor[("Executorbackend")] AssetManager["FAssets AssetManagercontract"] MAC["MasterAccountControllercontract"] PA["PersonalAccountcontract"] end User -- "1 - Payment + memo" --> Agent Agent -. "2 - observe payment" .-> Executor Executor -- "3 - executeDirectMinting" --> AssetManager AssetManager == "4 - mint FXRP +mintedFAssets callback" ==> MAC MAC -- "5 - forward FXRP +dispatch memo instruction" --> PA User -. "controls" .-> PA style Flare stroke-dasharray: 5 5 style XRPL stroke-dasharray: 5 5 ``` Direct-minting flow: XRPL `Payment` carrying a memo (1) → FAssets agent (2) → executor calls `AssetManager.executeDirectMinting` (3) → `AssetManager` mints FXRP and calls back into `mintedFAssets` (4) → `MasterAccountController` forwards FXRP and dispatches the memo to the user's `PersonalAccount` (5). ## Instructions on XRPL The Flare Smart Accounts allow XRPL users to perform actions on the Flare chain through instructions on the XRPL. This is done through a `Payment` to an XRPL address, designated by the operator, a backend monitoring incoming transactions and interacting with the Flare chain. The payment must transfer sufficient funds, as specified by the operator, and must include the proper payment reference (proof-based flow) or memo (direct-minting flow). The payment reference is a `bytes32` value. The first byte is reserved for the instruction code. The second byte is a wallet identifier. This is a number assigned to wallet providers by the Flare Foundation, and should otherwise be `0`. The remaining 30 bytes are the parameters for the chosen instruction. In practice, the payment reference should be prepared by a backend, through a web form. The first, instruction code, byte is further subdivided into two half-bytes. The first nibble is the instruction type. This is either `FXRP`, `Firelight`, or `Upshift` (with corresponding type IDS `0`, `1`, and `2`). The second nibble is the instruction command; the available commands are different for each instruction type. For the direct-minting flow, the memo carries a different layout — see the [Custom Instruction guide](/smart-accounts/custom-instruction) for the `PackedUserOperation` format and the `0xFF`/`0xE0`/`0xE1`/`0xE2`/`0xD0`/`0xD1` instruction codes.
Table of instruction IDs and corresponding actions. ### FXRP Instructions for interacting with the `FXRP` token. **Type ID:** `00`. | Command ID | Action | Description | | ---------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `00` | `collateralReservation` | Reserve a `value` of lots of collateral in the agent vault, registered with the `agentVaultId` with the `MasterAccountController` contract. | | `01` | `transfer` | Transfer a `value` (in drops) of FXRP to the `recipientAddress`. | | `02` | `redeem` | Redeem a `value` of lots of FXRP. | ### Firelight Instructions for interacting with a Firelight-type vault. **Type ID:** `01`. | Command ID | Action | Description | | ---------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `00` | `collateralReservationAndDeposit` | Reserve a `value` of lots of collateral in the agent vault, registered with the `agentVaultId` with the `MasterAccountController` contract. After successful minting, deposit the FXRP into the Firelight type vault, registered with the `vaultId` with the `MasterAccountController` contract. Equivalent to sending a `collateralReservation` instruction and a Firelight `deposit` instruction. | | `01` | `deposit` | Deposit a `value` of FXRP into the Firelight type vault, registered with the `vaultId` with the `MasterAccountController` contract. | | `02` | `redeem` | Start the withdrawal process for a `value` of vault shares (in drops) from the Firelight type vault, registered with the `vaultId`, with the `MasterAccountController` contract. | | `03` | `claimWithdraw` | Withdraw the `FXRP`, requested in the `value` period, from the Firelight type vault, registered with the `vaultId`, with the `MasterAccountController` contract. | ### Upshift Instructions for interacting with an Upshift-type vault. **Type ID:** `02`. | Command ID | Action | Description | | ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `00` | `collateralReservationAndDeposit` | Reserve a `value` of lots of collateral in the agent vault, registered with the `agentVaultId` with the `MasterAccountController` contract. After successful minting, deposit the FXRP into the Upshift type vault, registered with the `vaultId`, with the `MasterAccountController` contract. Equivalent to sending a `collateralReservation` instruction and a Upshift `deposit` instruction. | | `01` | `deposit` | Deposit a `value` of FXRP into the Upshift type vault, registered with the `vaultId` with the `MasterAccountController` contract. | | `02` | `requestRedeem` | Start the withdrawal process for a `value` of vault shares (in drops) from the Upshift type vault, registered with the `vaultId` with the `MasterAccountController` contract. | | `03` | `claim` | Withdraw the `FXRP` requested for the `value` date (encoded as `yyyymmdd`) from the Upshift type vault, registered with the `vaultId` with the `MasterAccountController` contract. |
## Dispatch on Flare ### Proof-based flow The operator monitors incoming transactions to the specified XRPL address. Upon receiving a payment, it requests a [`Payment` attestation](/fdc/attestation-types/payment) from the FDC and submits the proof together with the user's XRPL address to the appropriate facet on the `MasterAccountController`: - [`reserveCollateral`](/smart-accounts/reference/IMasterAccountController#reservecollateral) — for command `00` of any instruction type. Takes the payment reference and XRPL transaction ID (no FDC proof needed at this stage, the user has only committed to mint). - [`executeDepositAfterMinting`](/smart-accounts/reference/IMasterAccountController#executedepositafterminting) — second leg of the Firelight/Upshift collateral-reservation-and-deposit instructions, after FAssets minting completes. - [`executeInstruction`](/smart-accounts/reference/IMasterAccountController#executeinstruction) — for all other reference-encoded instructions. For the proof-bearing functions, the contract verifies the FDC proof and checks that: - the receiving address matches one of the registered operator XRPL addresses, - the source address matches the XRPL address passed to the function, - the XRPL transaction ID has not already been used (replay protection via `usedTransactionIds`). The contract then resolves the XRPL user's `PersonalAccount` from the address mapping, deploying it via `CREATE2` if it does not yet exist, and dispatches the instruction encoded in the payment reference. ### Direct-minting flow When the user mints FXRP directly to their smart account via [FAssets direct minting](/fassets/direct-minting), the FAssets `AssetManager` calls back into [`mintedFAssets`](/smart-accounts/reference/IMasterAccountController#mintedfassets) on `MemoInstructionsFacet`. The facet enforces that the caller is the `AssetManager`, resolves (or deploys) the user's `PersonalAccount`, pays an executor fee out of the minted FAssets, forwards the remainder to the personal account, and dispatches any memo instruction (`0xFF`, `0xE0`, `0xE1`, `0xE2`, `0xD0`, `0xD1`). ## Actions on Flare The XRPL user's smart account performs the actions in the instructions. This can be any of the instructions listed above, reserving collateral for minting FXRP, transferring FXRP to another address, redeeming FXRP, depositing it into a vault ... Furthermore, custom instructions (`0xFF`) can be executed — arbitrary function calls on Flare, encoded as an EIP-4337 `PackedUserOperation` carried in the XRPL memo and replayed on-chain by the personal account. Authorization comes from the XRPL `Payment` signature itself; the on-chain check only validates the `sender` and `nonce` fields of the `PackedUserOperation`. See the [Custom Instruction guide](/smart-accounts/custom-instruction) for the details. ## Video Tutorials --- ## FAsset Instructions In this guide, we will take a closer look at the Flare Smart Accounts instructions related to FAssets. FAsset is a trustless, over-collateralized bridge between XRPL and the Flare chain. The FXRP is a token representation of XRP on the Flare chain. FAssets allow XRPL users to partake in Flare's DeFi ecosystem using their XRP assets, without exchanging them for the FLR token. Several steps of the FAsset process are performed as transactions on the Flare chain, which requires users to hold FLR tokens for paying gas fees. The Flare Smart Accounts sidestep this by allowing XRPL users to trigger these actions through `Payment` transactions on XRPL. Thus, they eradicate completely the need for XRPL users to hold any non-XRPL assets. You can learn more about the FAssets in the [dedicated section](/fassets/overview/). The instructions fall under three types: `FXRP`, `Firelight`, and `Upshift`. Each type supports different instruction commands. The `FXRP` commands allow interaction with the base FAssets system, whereas `Firelight` and `Upshift` give access to actions pertaining to the corresponding vault architectures. The first byte of each encoded instruction is the instruction ID. It is further subdivided into the instruction type (first half-byte), and instruction command (second half-byte). The values for the instruction type Id nad the command ID range from `0` to `f` (`15` in hexadecimal); thus the instruction IDs range from `00` to `ff`. The second byte is the `walletID`. This value is assigned individually by the Flare, and is intended for wallet identification by the operator. ## FXRP The commands of the `FXRP` type give access to base FAsset actions. The ID of the `FXRP` type is `0`. ### `00` Collateral reservation Collateral reservation is the first step in the FAssets minting process. In anticipation of the transfer of XRP to the agent's underlying address on XRPL, a portion of its collateral is locked to prevent accidental under-collateralization. Since this action happens on the Flare chain, the gas fees need to be paid in the native FLR token. The collateral reservation action reserves a `value` of lots of collateral in the agent vault with the `agentVaultId`. The `MasterAccountController` keeps a registry of agent vault addresses, and the `agentVaultId` corresponds to one of the keys of that registry. The process is performed by an `executor` with `executorFee` (explained in the [minting guide](/fassets/minting#executor-role)); it is defined by the `MasterAccountController` contract. A collateral reservation payment reference is a 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `00` in the first nibble, - command ID `00` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - byte representation of `agentVaultId` in the next two bytes - the remaining 18 bytes are ignored and can have an arbitrary value ### `01` Transfer Transfer `value` of drops of FXRP to the `recipientAddress`. The FXRP is sent from the personal account that belongs to the XRPL address that sent the instruction, if the FXRP balance is sufficient. A transfer payment reference is a 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `00` in the first nibble, - command ID `01` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - `recipientAddress` in the remaining 20 bytes ### `02` Redeem Redemption is the process of converting FXRP back to XRP. A `value` of lots of FXRP are burned from the personal account belonging to the XRPL address that sent the instruction; and the same `value` of lots of XRP is returned to the user's XRPL account. The process is performed by an `executor` with `executorFee` (explained in the [minting guide](/fassets/minting#executor-role)); both of these are defined by the `MasterAccountController` contract. A redeem payment reference is the 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `00` in the first nibble, - command ID `02` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - the remaining 20 bytes are ignored and can have an arbitrary value ## Firelight The commands of the `Firelight` type allow interaction with Firelight-type vaults. The ID of the `Firelight` type is `1`. ### `00` Collateral reservation and deposit Reserve the collateral for `value` lots of FXRP with the agent's vault, registered with the `agentVaultId` with the `MasterAccountController` contract. When minting is completed, deposit `value` lots of FXRP into a Firelight-type vault at the address registered with the `MasterAccountController` contract with the `vaultId`. Essentially, this allows an XRPL user to signal intent for two actions at once; it is equivalent to sending a collateral reservation instruction first, and afterwards a deposit instruction. This cuts the total waiting time in half and reduces the total number of transactions by one. A collateral reservation and deposit instruction payment reference is the 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `01` in the first nibble, - command ID `00` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - byte representation of `agentVaultId` in the next 2 bytes - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value ### `01` Deposit Deposit `value` lots of FXRP into a Firelight-type vault at the address registered with the `MasterAccountController` contract with the `vaultId`. A deposit instruction payment reference is the 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `01` in the first nibble, - command ID `01` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - the next two bytes are ignored and can have an arbitrary value - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value ### `02` Redeem The first part of the withdrawal process from a Firelight vault. Initiate a withdrawal request of `value` of FXRP from the Firelight vault at the address registered with the `MasterAccountController` contract with the `vaultId`. A redeem instruction payment reference is the 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `01` in the first nibble, - command ID `02` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - the next two bytes are ignored and can have an arbitrary value - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value ### `03` Claim withdraw The second part of the withdrawal process from a Firelight vault. Claim the pending withdrawal from the `value` period, from the Firelight vault at the address registered with the `MasterAccountController` contract with the `vaultId`. The assets are transferred to the user's smart account. The action can only be performed after one full period has passed. The `value` is a parameter, emitted by the Firelight vault upon a redemption request. A claim withdrawal payment reference is a 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `01` in the first nibble, - command ID `03` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - the next two bytes are ignored and can have an arbitrary value - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value ## Upshift The commands of the `Upshift` type allow interaction with Upshift-type vaults. The ID of the `Upshift` type is `2`. ### `00` Collateral reservation and deposit Reserve the collateral for `value` lots of FXRP with the agent's vault, registered with the `agentVaultId` with the `MasterAccountController` contract. When minting is completed, deposit `value` lots of FXRP into an Upshift-type vault at the address registered with the `MasterAccountController` contract with the `vaultId`. Essentially, this allows an XRPL user to signal intent for two actions at once; it is equivalent to sending a collateral reservation instruction first, and afterwards a deposit instruction. This cuts the total waiting time in half and reduces the total number of transactions by one. A collateral reservation and deposit instruction payment reference is the 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `02` in the first nibble, - command ID `00` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - byte representation of `agentVaultId` in the next 2 bytes - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value ### `01` Deposit Deposit `value` lots of FXRP into an Upshift type vault at the address registered with the `MasterAccountController` contract with the `vaultId`. A deposit instruction payment reference is the 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `02` in the first nibble, - command ID `01` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - the next two bytes are ignored and can have an arbitrary value - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value ### `02` Request redeem The first part of the withdrawal process from an Upshift vault. Initiate a withdrawal request of `value` of FXRP from the Upshift vault at the address registered with the `MasterAccountController` contract with the `vaultId`. A request redeem instruction payment reference is the 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `02` in the first nibble, - command ID `02` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - the next two bytes are ignored and can have an arbitrary value - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value ### `03` Claim The second part of the withdrawal process from an Upshift vault. Claim the pending withdrawal from the `value` period, from the Upshift vault at the address registered with the `MasterAccountController` contract with the `vaultId`. The assets are transferred to the user's smart account. The action can only be performed after one full period has passed. The `value` represents the date when the redemption was requested. It is an 8-digit number `YYYYMMDD`, where `YYYY` is the year, `MM` the month (from `01` to `12`), and `DD` the day (from `01` to `31`, with additional monthly constraints) of the request. A claim payment reference is a 32-byte value: - byte representation of the instruction ID in the first byte: - type ID `02` in the first nibble, - command ID `03` in the second nibble, - byte representation of `walletId` in the second byte - byte representation of `value` in the next 10 bytes - the next two bytes are ignored and can have an arbitrary value - byte representation of `vaultId` in the next 2 bytes - the remaining 16 bytes are ignored and can have an arbitrary value --- ## Custom Instruction Flare Smart Accounts let an XRPL user execute arbitrary contract calls on Flare trough the [`Payment`](https://xrpl.org/docs/references/protocol/transactions/types/payment) transaction on the XRPL blockchain. Each personal account exposes an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) style `executeUserOp` entry point that the [`MasterAccountController`](/smart-accounts/reference/IMasterAccountController) invokes when it processes a custom instruction memo. Custom instructions carry the full call payload in the XRPL **memo** field. The user signs a `PackedUserOperation`, ships it on XRPL, and the smart account replays it on the Flare blockchain. :::warning No destination tags XRPL transactions targeting smart accounts must not use a destination tag. A destination tag forces [FAssets direct minting](/fassets/direct-minting) to credit the tag-holder, which would let an unrelated party front-run the user operation. ::: ## User operation payload The custom instruction payload has two layers: the outer EIP-4337 [`PackedUserOperation`](https://eips.ethereum.org/EIPS/eip-4337#useroperation) carried in the XRPL memo, and the inner [`executeUserOp(Call[])`](/smart-accounts/reference/IPersonalAccount#executeuserop) that the personal account runs once the controller dispatches it. Only three fields from the [`PackedUserOperation`](https://eips.ethereum.org/EIPS/eip-4337#useroperation) struct are required for Flare Smart Accounts: - `sender` **must** equal the address of the personal account derived from the XRPL sender. Use [`getPersonalAccount`](/smart-accounts/reference/IMasterAccountController#getpersonalaccount) to look it up — the address is deterministic, so you can fetch it before the account is even deployed. - `nonce` **must** equal the personal account's current nonce returned by [`getNonce`](/smart-accounts/reference/IMasterAccountController#getnonce). The nonce auto-increments on every successful execution to prevent replay. - `callData` is the calldata that the controller invokes on the personal account. In practice, this is `abi.encodeCall(IPersonalAccount.executeUserOp, (calls))` — anything else either reverts or is rejected by the personal account's `onlyController` modifier. The remaining fields are not validated on-chain and can be left empty. Authorization comes from the XRPL signature on the `Payment` XRPL payment transaction itself: only the XRPL key for `sender`'s `xrplOwner` can deliver the memo. If the personal account has pinned an executor via [`getExecutor`](/smart-accounts/reference/IMasterAccountController#getexecutor), only that executor is permitted to relay the mint. ### `executeUserOp` and the `Call` struct The personal account's [`executeUserOp(Call[])`](/smart-accounts/reference/IPersonalAccount#executeuserop) runs each entry in the `Call[]` in order, forwarding it with its supplied `value` and `data`: ```solidity struct Call { address target; uint256 value; bytes data; } function executeUserOp(Call[] calldata _calls) external payable; ``` Each call is dispatched with the personal account as `msg.sender`. If any call reverts, the whole user operation reverts with [`CallFailed`](/smart-accounts/reference/IPersonalAccount#callfailed) — partial execution is not possible. The `executeUserOp` function is `payable`, so the user operation can forward native tokens (e.g. FLR) alongside the calls. To fund the personal account, send FLR to the address using the [Flare faucet](https://faucet.flare.network/). ### Building `callData` in TypeScript You can build the `callData` in TypeScript using the [`encodeFunctionData`](https://viem.sh/docs/contract/encodeFunctionData#encodefunctiondata) function from the `viem` library: ```typescript const calls = [ { target: counterAddress, value: 0n, data: encodeFunctionData({ abi: counterAbi, functionName: "increment", args: [], }), }, ]; const callData = encodeFunctionData({ abi: personalAccountAbi, functionName: "executeUserOp", args: [calls], }); ``` The encoded `callData` becomes the `callData` field of the `PackedUserOperation` struct placed in the XRPL payment memo field. ## Replay protection Each personal account maintains a monotonically increasing nonce, accessible via [`getNonce`](/smart-accounts/reference/IMasterAccountController#getnonce). A successful `executeUserOp` increments the nonce and emits [`UserOperationExecuted`](/smart-accounts/reference/IMasterAccountController#useroperationexecuted), so the same `PackedUserOperation` cannot be re-executed. ## Failure Handling The whole pipeline is atomic with respect to the user operation: - If `sender` does not match the personal account, the call reverts with [`InvalidSender`](/smart-accounts/reference/IMasterAccountController#invalidsender). - If `nonce` is not the expected value, it reverts with [`InvalidNonce`](/smart-accounts/reference/IMasterAccountController#invalidnonce). - If the memo body has the wrong length for its instruction ID, it reverts with [`InvalidMemoData`](/smart-accounts/reference/IMasterAccountController#invalidmemodata); an unrecognized instruction byte reverts with [`InvalidInstructionId`](/smart-accounts/reference/IMasterAccountController#invalidinstructionid). - If any inner call reverts, the personal account surfaces it as [`CallFailed`](/smart-accounts/reference/IPersonalAccount#callfailed) and the entire user operation reverts. Because the FXRP transfer is performed before the memo is decoded, **the mint succeeds even if the user operation reverts** — see [`DirectMintingExecuted`](/smart-accounts/reference/IMasterAccountController#directmintingexecuted). The freshly minted FXRP remains in the personal account, and the user can either re-submit a fixed user operation or transfer the FXRP to another address via standard [FAssets instructions](/smart-accounts/fasset-instructions). --- ## Smart Accounts Reference ## Deployed Contracts :::info The `MasterAccountController` address is the same on all Flare [networks](/network/overview). Retrieve it from the [Flare contracts registry](/network/guides/flare-contracts-registry) rather than hardcoding it, so each XRPL address maps to the same personal account everywhere. ::: ## References Smart contract interfaces for interacting with the Flare Smart Accounts system. --- ## Introduction The [Flare Smart Accounts CLI](https://github.com/flare-foundation/smart-accounts-cli) is a tool written in Python that streamlines the Flare Smart Accounts process by properly structuring and sending XRPL transactions/instructions. It has commands corresponding to each Flare Smart Accounts action ([table of instructions](/smart-accounts/overview#instructions-on-xrpl)), as well as other useful debugging features. :::note The CLI executes **only** the transaction on XRPL. It does not bridge the instructions to Flare. That is done by the operator - a service that Flare runs. ::: To use the CLI, first clone [the repository](https://github.com/flare-foundation/smart-accounts-cli) and install the necessary dependencies: ```sh git clone https://github.com/flare-foundation/smart-accounts-cli.git cd smart-accounts-cli pip install -r requirements.txt ``` Copy the `.env.example` file to the `.env` file, and fill in the missing values in the latter. ```sh cp .env.example .env ``` The missing values are an XRPL testnet account secret value, a Flare address private key, and the RPC URLs for both Flare's Coston2 and XRPL testnet. You can create a new secret string through the [XRP faucets](https://xrpl.org/resources/dev-tools/xrp-faucets). The script is run in the terminal with a command of the following shape. ```sh ./smart_accounts.py ... ``` Here is a list of available commands and their positional arguments: - `encode` - `fxrp-cr`: encode FXRP collateral reservation instruction - `fxrp-transfer`: encode FXRP transfer instruction - `fxrp-redeem`: encode FXRP redeem instruction - `firelight-cr-deposit`: encode Firelight collateral reservation and deposit instruction - `firelight-deposit`: encode Firelight deposit instruction - `firelight-redeem`: encode Firelight redeem instruction - `firelight-claim-withdraw`: encode Firelight claim withdraw instruction - `upshift-cr-deposit`: encode Upshift collateral reservation and deposit instruction - `upshift-deposit`: encode Upshift deposit instruction - `upshift-request-redeem`: encode Upshift request redeem instruction - `upshift-claim`: encode Upshift claim instruction - `decode` - `bridge` - `instruction`: send imputed instruction as a transaction on XRPL - `mint-tx`: make a deposit to an agent's vault The positional arguments of the `encode` command correspond to type-command instruction pairs, described in the [FAsset instructions guide](/smart-accounts/fasset-instructions). The `bridge` command either makes an XRPL Payment transaction with the imputed instruction as payment reference, or transfers funds to an agent's XRPL vault as part of the minting process. ## Using the CLI The below-described CLI commands are intended to work in tandem with one another. The output of the `encode` command can be sent to the `bridge` command via stdin. Commands can be piped in the following way. ```sh | ./smart_accounts.py bridge instruction - ``` If the instruction includes a minting step (`fxrp-cr`, `firelight-cr-deposit` and `upshift-cr-deposit`), we can also order the script to make a mint transaction that matches the output of the `bridge instruction`. ```sh | ./smart_accounts.py bridge instruction - | ./smart_accounts.py bridge mint-tx --wait - ``` This is described in more detail in the [Bridge section](/smart-accounts/guides/cli/introduction#bridge) of this guide. ## Encode Encode and instruction with given parameters. The parameters are unique to each command. There is one parameter that all the commands have in common, `walletId`. The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode --wallet-id ... ``` ### FXRP Smart account instructions for interacting with the base FAsset system. Each of these commands starts with the `fxrp` keyword. #### Collateral reservation Reserve `value` lots of collateral with the Reserve `value` lots of FXRP to the user's smart account. The script first reserves collateral with the agent with the `address`, by sending a `reserveCollateral` instruction. It then sends a `lots` amount of XRP to the agent's underlying address. An executor, determined by the `MasterAccountController`, will complete the minting process, and `lots` of FXRP will be minted to the user's smart account. The list of all the available agents and their IDs can be obtained from the `MasterAccountController` contract. {/* TODO:(Nik) Write a separate guide in which you explain how to do this, and link to it here. */} ```markup ./smart_accounts.py encode fxrp-cr --wallet-id --value --agent-vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode fxrp-cr --wallet-id 0 --value 1 --agent-vault-id 1 ``` The above command produces the following output: ```sh 0x0000000000000000000000010001000000000000000000000000000000000000 ```
#### Transfer Transfer `value` FXRP from your personal account to the `recipientAddress` on Flare. The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode fxrp-transfer \ --wallet-id \ --value \ --recipient-address ```
Example values and expected output ```sh ./smart_accounts.py encode fxrp-transfer \ --wallet-id 0 \ --value 1 \ --recipient-address "0xf5488132432118596fa13800b68df4c0ff25131d" ``` The above command produces the following output: ```sh 0x010000000000000000000001f5488132432118596fa13800b68df4c0ff25131d ```
#### Redeem Redeem `value` lots of FXRP back to XRP. That number of lots of FXRP are burned from the user's smart account, and the same amount of XRP is sent to the user's address on XRPL. The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode fxrp-redeem --wallet-id --value ```
Example values and expected output ```sh ./smart_accounts.py encode fxrp-redeem --wallet-id 0 --value 1 ``` The above command produces the following output: ```sh 0x0200000000000000000000010000000000000000000000000000000000000000 ```
### Firelight Smart account instructions for interacting with a Firelight-type vault. Each of these commands starts with the `firelight` keyword. #### Collateral reservation and deposit The collateral reservation and deposit instruction signals two intents at once. First, to reserve collateral in an agent's vault, registered with the `MasterAccountController` contract with the `agentVaultId`, the necessary collateral to mint `value` lots of FXRP. And second, to deposit the newly minted FXRP into a Firelight-type vault, registered with the `MasterAccountController` contract with the `vaultId`. The purpose of this instruction is to streamline the minting and deposit process by cutting out an instruction XRPL transaction and the wait that that incurs. In essence, it combines the FXRP collateral reservation instruction and the Firelight deposit instruction. {/* TODO:(Nik) prepare as separate in-depth guide for cr-and-deposit, that goes through all the steps. Link to that guide here. */} The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markdown ./smart_accounts.py encode firelight-cr-deposit \ --wallet-id \ --value \ --agent-vault-id \ --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode firelight-cr-deposit --wallet-id 0 --value 1 --agent-vault-id 1 --vault-id 1 ``` The above command produces the following output: ```sh 0x1000000000000000000000010001000100000000000000000000000000000000 ```
#### Deposit Deposit `vault` drops of FXRP into the Firelight-type vault, registered with the `MasterAccountController` contract with the `vaultId`. The complete list of the registered vaults can be obtained from the `MasterAccountController` contract. {/* TODO:(Nik) Write a separate guide in which you explain how to do this, and link to it here. */} :::warning The registered vaults can be of either the Firelight- or Upshift-type, so make sure that you choose the right one for the instruction you are sending. ::: The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode firelight-deposit --wallet-id --value --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode firelight-deposit --wallet-id 0 --value 1 --vault-id 1 ``` The above command produces the following output: ```sh 0x1100000000000000000000010000000100000000000000000000000000000000 ```
#### Redeem Begin the withdrawal process from the Firelight-type vault. A `value` of drops FXRP is marked to be withdrawn from the Firelight-type vault, registered with the `MasterAccountController` contract with the `vaultId`, to the user's personal account. {/* TODO:(Nik) name of the event */} In the process, the vault emits an event with the `period` parameter. This value is required in the second part of the withdrawal process. The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode firelight-redeem --wallet-id --value --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode firelight-redeem --wallet-id 0 --value 1 --vault-id 1 ``` The above command produces the following output: ```sh 0x1200000000000000000000010000000100000000000000000000000000000000 ```
#### Claim withdraw Complete the withdrawal process from the Firelight-type vault, registered with the `MasterAccountController` contract with the `vaultId`. After the withdrawal process has been started, the funds are locked for a certain amount of time. Once that period has passed, they can be transferred back to the user's personal account. {/* TODO */} When the withdrawal is requested, a period value is exposed in the event that is emitted. The period must be included in the claim withdrawal instruction as the `value` parameter. The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode firelight-claim-withdraw \ --wallet-id \ --value \ --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode firelight-claim-withdraw --wallet-id 0 --value 123456789 --vault-id 1 ``` The above command produces the following output: ```sh 0x1300000000000000075bcd150000000100000000000000000000000000000000 ```
### Upshift Smart account instructions for interacting with an Upshift-type vault. Each of these commands starts with the `upshift` keyword. #### Collateral reservation and deposit The collateral reservation and deposit instruction signals two intents at once. First, to reserve collateral in an agent's vault, registered with the `MasterAccountController` contract with the `agentVaultId`, the necessary collateral to mint `value` lots of FXRP. And second, to deposit the newly minted FXRP into an Upshift-type vault, registered with the `MasterAccountController` contract with the `vaultId`. The purpose of this instruction is to streamline the minting and deposit process by cutting out an instruction XRPL transaction and the wait that that incurs. In essence, it combines the FXRP collateral reservation instruction and the Upshift deposit instruction. {/* TODO:(Nik) prepare as separate in-depth guide for cr-and-deposit, that goes through all the steps. Link to that guide here. */} The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markdown ./smart_accounts.py encode upshift-cr-deposit \ --wallet-id \ --value \ --agent-vault-id \ --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode upshift-cr-deposit --wallet-id 0 --value 1 --agent-vault-id 1 --vault-id 2 ``` The above command produces the following output: ```sh 0x2000000000000000000000010001000200000000000000000000000000000000 ```
#### Deposit Deposit `vault` drops of FXRP into the Upshift-type vault, registered with the `MasterAccountController` contract with the `vaultId`. The complete list of the registered vaults can be obtained from the `MasterAccountController` contract. {/* TODO:(Nik) Write a separate guide in which you explain how to do this, and link to it here. */} :::warning The registered vaults can be of either the Firelight- or Upshift-type, so make sure that you choose the right one for the instruction you are sending. ::: The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode upshift-deposit --wallet-id --value --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode upshift-deposit --wallet-id 0 --value 1 --vault-id 2 ``` The above command produces the following output: ```sh 0x2100000000000000000000010000000200000000000000000000000000000000 ```
#### Request redeem Begin the withdrawal process from an Upshift-type vault. A `value` of drops FXRP is marked to be withdrawn from the Upshift-type vault, registered with the `MasterAccountController` contract with the `vaultId`, to the user's personal account. {/* TODO:(Nik) name of the event */} In the process, the vault emits an event with the `period` parameter. This value is required in the second part of the withdrawal process. The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode upshift-request-redeem \ --wallet-id \ --value \ --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode upshift-request-redeem --wallet-id 0 --value 1 --vault-id 2 ``` The above command produces the following output: ```sh 0x2200000000000000000000010000000200000000000000000000000000000000 ```
#### Claim Complete the withdrawal process from the Upshift-type vault, registered with the `MasterAccountController` contract with the `vaultId`. After the withdrawal process has been started, the funds are locked for a certain amount of time. Once that period has passed, they can be transferred back to the user's personal account. The `value` parameter is the date when the redemption was requested. It should be given as an 8-digit number `YYYYMMDD`, where `YYYY` is the year, `MM` the month (from `1` to `12`) and `DD` (from `1` to `31`) the day of the redemption request. The `walletId` is a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. If no number has been provided to you, set the `walletId` to `0`. ```markup ./smart_accounts.py encode upshift-claim --wallet-id --value --vault-id ```
Example values and expected output ```sh ./smart_accounts.py encode upshift-claim --wallet-id 0 --value 20260124 --vault-id 2 ``` The above command produces the following output: ```sh 0x23000000000000000135251c0000000200000000000000000000000000000000 ```
## Bridge The bridge command makes an XRPL Payment transaction on behalf of the user, with the given input as the transaction memo field. There are two positional arguments, `instruction` and `mint-tx`. The `instruction` positional argument signals the user's intent by making an XRPL Payment transaction to the operator's XRPL address, with the instruction attached as the memo field. The `mint-tx` argument performs the second step of the minting process, sending the XRPL to the agent's vault address on XRPL (you can learn more about the minting process in [the minting guide](/fassets/minting)). ### Instruction When the `bridge` command is used with the `instruction` positional argument, it accepts an `encodedInstruction` as input. It makes a Payment transaction to the operator's XRPL address, with the `encodedInstruction` as the memo field of the payment. Finally, it prints the hash of the transaction to the standard output stream. The `encodedInstruction` can be obtained with the `encode` command. ```markup ./smart_accounts.py bridge instruction ``` Alternatively, the command can read the input directly from the standard input stream. Since the `encode` command prints the result to the standard output stream, the two commands can be chained together. To do this, the `-` character is used instead of the `transactionHash`. ```sh | ./smart_accounts.py bridge instruction - ```
Example values and expected output ```sh ./smart_accounts.py encode fxrp-redeem --wallet-id 0 --value 1 \ | ./smart_accounts.py bridge instruction - ``` The above command encodes an FXRP redeem instruction for `1` lot of FXRP, and sends that instruction as an XRPL Payment transaction. It produces the following output. ```sh sent bridge instruction transaction: CD0879CD7398DE4B96A7A7D475EFD0F3585E05D100C84C9E3FE32B34FACF0E39 CD0879CD7398DE4B96A7A7D475EFD0F3585E05D100C84C9E3FE32B34FACF0E39 ``` The instruction only works if the user's personal account has sufficient FXRP balance.
### Mint tx When the `bridge` command is used with the `mint-tx` positional argument, it accepts an XRPL `transactionHash` as input. It then observes the `MasterAccountController` contract for a `CollateralReserved` event that belongs to the transaction with the `transactionHash` A payment reference is read from the `CollateralReserved` event, and a mint transaction to the agent vault's address is performed. The transaction must contain a collateral reservation (and deposit) instruction, otherwise the process fails, since no such event is emitted for other instructions. The `transactionHash` can be obtained by calling the `bridge` command with the `instruction` positional argument. ```markup ./smart_accounts.py bridge mint-tx ``` Alternatively, the command can read the input directly from the standard input stream. Since the `encode` and `bridge` commands both print the result to the standard output stream, all three commands can be chained together. To do this, the `-` character is used instead of the `transactionHash`. Additionally, the `--wait` flag must be provided. ```markup | ./smart_accounts.py bridge instruction - | ./smart_accounts.py bridge mint-tx --wait - ```
Example values and expected output ```sh ./smart_accounts.py encode fxrp-cr --wallet-id 0 --value 1 --agent-vault-id 1 \ | ./smart_accounts.py bridge instruction - \ | ./smart_accounts.py bridge mint-tx --wait - ``` The above command encodes an FXRP collateral reservation instruction for `1` lot of FXRP, using the agent vault with ID `1`, and sends that instruction as an XRPL Payment transaction. It then waits It produces the following output. ```sh sent bridge instruction transaction: 659364520199DED473635A9ABFB1C15B945B216A1208679979AB5C05F03E837C sent mint tx: 8401244DD1FA4C295ABC16A0B05CC12F7DBCE34756F12C2F5C3DC1840A5F3316 8401244DD1FA4C295ABC16A0B05CC12F7DBCE34756F12C2F5C3DC1840A5F3316 ``` The instruction only works if the user's personal account has sufficient FXRP balance.
## Decode The `decode` command is the inverse of the `encode` command. It takes an `encodedInstruction` as input, and decodes it to a Python class that corresponds to the instruction. The `encodedInstruction` can be obtained from the output of the `encode` command. ```markdown ./smart_accounts.py decode ``` Alternatively, the command can read the input directly from the standard input stream. Since the `encode` command prints the result to the standard output stream, the two commands can be chained together. To do this, the `-` character is used instead of the `transactionHash`. ```markdown | ./smart_accounts.py decode - ```
Example values and expected output ```sh ./smart_accounts.py encode fxrp-redeem --wallet-id 0 --value 1 \ | ./smart_accounts.py decode - ``` The above command encodes an FXRP redeem instruction for `1` lot of FXRP, and then decodes it to an `FxrpRedeem` class. It produces the following output. ```Python FxrpRedeem(wallet_id=0, value=1) ```
--- ## FAssets Cycle In this guide, we will walk you through all aspects of the [FAssets](/fassets/overview) process. We will start with an account on XRPL, convert some of the XRP to FXRP on Flare, deposit it into an Upshift-type vault, redeem it from the vault, and then redeem FXRP back to XRP. The steps we will take will be as follows: 1. **mint and deposit:** convert XRP to FXRP and deposit it to an Upshift-type vault 2. **request redeem:** start the withdrawal process of the deposited FXRP the Upshift-type vault 3. **claim:** finish the withdrawal process of FXRP from the Upshift-type vault 4. **redeem:** convert FXRP back to XRP We will do all of that through the [Flare Smart Accounts CLI](/smart-accounts/guides/cli/introduction). The CLI allows us to make XRPL transactions through terminal commands. :::note The idea behind the Flare Smart Accounts is that we perform actions on the Flare chain through instructions sent on XRPL. ::: The same process can be repeated for a Firelight-type vault, but in this guide, we will only focus on an Upshift-type vault. ## Mint and deposit First, we mint FXRP by reserving the necessary collateral and then sending XRP to the agent's underlying address. We will mint `1` lot of FXRP, with the agent at the address whose vault has the ID `1`. Then, we deposit `10` FXRP into the Upshift-type vault, registered with the `MasterAccountController` with the ID `2`. The CLI command that does this is: ```sh ./smart_accounts.py encode upshift-cr-deposit --wallet-id 0 --value 1 --agent-vault-id 1 --vault-id 2 \ | ./smart_accounts.py bridge instruction - \ | ./smart_accounts.py bridge mint-tx --wait - ```
Expected output ``` sent bridge instruction transaction: 77539CDED3BD58E151CD0000EEC611A43A5539620B7CE4198BB3D63B031E9818 sent mint tx: 3C65E10D609AB3CC1DBD03C96E401704123C0630D8AE5622B651A1E0159C1D38 3C65E10D609AB3CC1DBD03C96E401704123C0630D8AE5622B651A1E0159C1D38 ```
The CLI sends both the Upshift collateral reservation and deposit instruction, and the `Payment` transaction to the agent's underlying address. We could perform the minting and deposit steps separately. To do so, we would do so with the following commands. But that would take an additional transaction and twice as long, so it is recommended that we avoid it unless we have a very specific reason why the first command is not suitable. ```sh ./smart_accounts.py encode fxrp-cr --wallet-id 0 --value 1 --agent-vault-id 1 \ | ./smart_accounts.py bridge instruction - \ | ./smart_accounts.py bridge mint-tx --wait - ``` The first command reserves `1` lot of collateral with the agent's vault with ID `1`, and makes a mint payment to its address. ```sh ./smart_accounts.py encode upshift-deposit --wallet-id 0 --value 10 --vault-id 2 \ | ./smart_accounts.py bridge instruction - ``` The second command deposits `10` FXRP into an Upshift-type vault registered with the ID `2`.
Expected outputs Mint: ```sh sent bridge instruction transaction: 75FB3ED006417FC1537432DA2D7180BD5BED93F23B988F77E32ED0804CD1A332 sent mint tx: 4BF8F3B32E234335F9EC6FE503E33B9EDF4E3E37D1E952DF28D260AC909609F1 4BF8F3B32E234335F9EC6FE503E33B9EDF4E3E37D1E952DF28D260AC909609F1 ``` Deposit: ```sh sent bridge instruction transaction: 404EE0470152513277B10E5ADB6388175010634CDAF1815979ED418CDBAD0C45 404EE0470152513277B10E5ADB6388175010634CDAF1815979ED418CDBAD0C45 ```
## Request redeem We withdraw the same number of FXRP tokens that we have just deposited from the vault (`10`). The same flow applies to both Upshift-type and Firelight-type vaults. The process involves two steps. Before we can withdraw the tokens, we need to request their withdrawal. In a production build, the tokens stay locked for a specified time. The CLI first sends the `withdraw` instruction, and afterwards, the `claimWithdraw`. The first instruction starts the withdrawal process, and the second claims the FXRP once it is released. To request the withdrawal of `10` FXRP from the Upshift-type vault with ID `2`, we use the command: ```sh ./smart_accounts.py encode upshift-request-redeem --wallet-id 0 --value 10 --vault-id 2 \ | ./smart_accounts.py bridge instruction - ```
Expected output ```sh sent bridge instruction transaction: 33B08253AE3907A8CE07EA3F5C9BE91EBCC6089339725A8BCFF371ED86F26238 33B08253AE3907A8CE07EA3F5C9BE91EBCC6089339725A8BCFF371ED86F26238 ```
## Claim After the waiting period has passed, we can claim the FXRP we requested. This is the second step of the withdrawal process. We need to specify the date when the redemption request was put in. We made the request on 28 Dec 2025, so the value parameter should be `20251228`. But in general, the value should be `YYYYMMDD`, where `YYYY` is the year, `MM` the month, and `DD` the date. To complete the withdrawal from the Upshift-type vault with ID `2`, we use the command: ```sh ./smart_accounts.py encode upshift-claim --wallet-id 0 --value 20251228 --vault-id 2 \ | ./smart_accounts.py bridge instruction - ```
Expected output ```sh sent bridge instruction transaction: 8D81F5A2625A927A6759646806BC8217147523F069899A4A2586562D6E0F105E 8D81F5A2625A927A6759646806BC8217147523F069899A4A2586562D6E0F105E ```
## Redeem The last step is to convert FXRP back to XRP. We will redeem the `1` lot of FXRP we minted in the first step back to XRP. We will invoke the CLI with the following command: ```sh ./smart_accounts.py encode fxrp-redeem --wallet-id 0 --value 1 \ | ./smart_accounts.py bridge instruction - ```
Expected output ```sh sent bridge instruction transaction: FE9D00397D8F11364C6E2792D62F801AA8BEF3499EB5068B624CAD1477ED7721 FE9D00397D8F11364C6E2792D62F801AA8BEF3499EB5068B624CAD1477ED7721 ```
--- ## Mint to any Address In this guide we will take a look at how we can use the [Flare smart accounts CLI](/smart-accounts/guides/cli/introduction) to perform a mint action, followed by the transfer of freshly minted FXRP. What this allows us to do is to, effectively, **mint FXRP onto someone else's Flare address**. This guide is aimed primarily at Web3 developers who desire to engage with the FAssets system but want to sidestep the base minting process. Let us suppose that we want to mint `1` lot of FXRP, and transfer it to the address `0xf5488132432118596fa13800b68df4c0ff25131d`. First, we need to prepare a collateral reservation instruction. We do that with the following CLI command. ```sh ./smart_accounts.py encode fxrp-cr --wallet-id 0 --value 1 --agent-vault-id 1 ``` The `--wallet-id` flag expects a Flare-designated parameter, intended for wallet identification by the operator. It has no impact on the result of the action performed. Because no number has been provided to us, we set it to `0`. We chose the agent vault with ID `1` from the list of available agents. {/* TODO:(Nik) Link to the guide about getting agent vault addresses. */}
Expected output ```sh 0x0000000000000000000000010001000000000000000000000000000000000000 ```
Then, we need to actually send the instruction as an XRPL Payment transaction to the operator's XRPL address. We pipe the output of the first command into a `bridge` command. But that is not, by itself, sufficient. To complete the minting step, we need to also transfer `1` lot worth of FXRP to the agent's vault we specified. We do that by piping the output of the second command - the hash of the transaction that carried our collateral reservation instruction - into the second `bridge` command, with the `mint-tx` positional argument. ```sh ./smart_accounts.py encode fxrp-cr --wallet-id 0 --value 1 --agent-vault-id 1 \ | ./smart_accounts.py bridge instruction - \ | ./smart_accounts.py bridge mint-tx --wait - ```
Expected output ```sh sent bridge instruction transaction: 08C2DD9EF3C0BB0A29D70F7E495EB3D96E1AA443B9100052FCCB44A176A9FBB8 sent mint tx: CD15241A6F0D2AFE5441C4FE3A2A9360109164CDCDC9EF3BC6A652D3C257DEA2 CD15241A6F0D2AFE5441C4FE3A2A9360109164CDCDC9EF3BC6A652D3C257DEA2 ```
Next, we prepare the transfer instruction. We will send `10` FXRP (which equals `1` lot) to the address `0xf5488132432118596fa13800b68df4c0ff25131d`. The encode CLI command that does this is: ```sh ./smart_accounts.py encode fxrp-transfer \ --wallet-id 0 \ --value 10 \ --recipient-address "0xf5488132432118596fa13800b68df4c0ff25131d" ```
Expected output ```sh 0x01000000000000000000000af5488132432118596fa13800b68df4c0ff25131d ```
Lastly, we send the instruction as an XRPL Payment transaction to the operator's XRPL address. We do this, again, by piping the output of the above `encode` command into a `bridge` command. ```sh ./smart_accounts.py encode fxrp-transfer \ --wallet-id 0 \ --value 10 \ --recipient-address "0xf5488132432118596fa13800b68df4c0ff25131d" \ | ./smart_accounts.py bridge instruction - ```
Expected output ```sh sent bridge instruction transaction: 9D5420C689DE7E0189BA15C4F874E491E4A08610A792472DF421B501DD5088AD 9D5420C689DE7E0189BA15C4F874E491E4A08610A792472DF421B501DD5088AD ```
With that, we have successfully minted `1` lot of FXRP, and transferred it to the Flare address `0xf5488132432118596fa13800b68df4c0ff25131d`. --- ## Custom Instructions {/* NOTE:(Nik) This article has been placed right after the introduction into Flare Smart Accounts instead of at the end, so that readers don't get bored with reading before they reach it. */} The Flare Smart Accounts allow XRPL users to make custom function calls on Flare through instructions sent on XRPL. In this guide, we will look at how the custom instructions can be developed using a mock version of the `MasterAccountController` contract. In a typical workflow, the user sends instructions as memo data on XRPL. Those then have to be verified by the FDC on the Flare chain. That process requires waiting, which is less than ideal in a development environment. For that reason, a mock version of the `MasterAccountController` contract has been deployed. It implements two additional functions: - `createFundPersonalAccount` - `executeCustomInstructionDevelopment` The `createFundPersonalAccount` function creates a new `PersonalAccount` for the user. It accepts as its argument a string `_xrplAddress`, which represents an address on XRPL. The string is then concatenated with the `msg.sender` value; the `PersonalAccount` is created for this address. Thus, a developer can create multiple XRPL "addresses". This allows them to more easily test the interactions between different personal accounts. The "address" is combined with their Flare address, so that they can give meaningful names to their "addresses" without the danger of multiple developers' addresses crashing. The `createFundPersonalAccount` function is a payable function. And funds sent with the transaction are deposited into the newly created personal account. That way, the personal account can interact with payable functions from the get-go. The `executeCustomInstructionDevelopment` function sidesteps the XRP transaction and the FDC `Payment` verification process. It allows developers to send an array of custom instructions to the `MasterAccountController` contract directly. The two parameters this function takes are the `_xrplAddress` string and an array of `IMasterAccountController.CustomInstruction` structs. It first concatenates the input string with the `msg.sender` value, the same way the `createFundPersonalAccount` function does. Then, it retrieves the `PersonalAccount` representing that address, and calls its `custom` function with the array of custom instructions it received as input. {/* TODO:(Nik) Explain how we can do these two things through the CLI. */} ## A simple example We will now use the Flare Smart Accounts CLI to interact with a simple contract. The contract `Foo` has a single payable function `bar`. The `bar` function accepts a `uint256` value as input, and adds the fee paid with the transaction to a mapping. ```Solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract Foo { mapping(uint256 => uint256) public map; function bar(uint256 a) public payable { map[a] = map[a] + msg.value; } } ``` We want to send `1 FLR` to the contract, and save it under the number `42`. The address of the `Foo` contract is `0x296432C15504Ed465fAce11E54Ce4aac50cCd8A3`. Using an online ABI-encoding tool, we get the following hash for the `bar` function, with `42` as input: `0x0423a132000000000000000000000000000000000000000000000000000000000000002a`. :::warning Encoding calldata by hand is error-prone. It is recommended to use established libraries, or an [online tool](https://abi.hashex.org/) (if you want to quickly check something). ::: There are two ways we can go about developing the custom instructions. We will start with an approach that is what the production code would take. Afterwards, we will use the mock functions to speed up the development. ### Normal approach Before we can execute the above instructions, we need to top up the smart account that will perform the function call. We run the following command, which fails because our account lacks funds on Flare. It is necessary to send some instructions, because that is what creates an account for us in the first place. ```sh ./smart_accounts.py bridge custom -a "0x296432C15504Ed465fAce11E54Ce4aac50cCd8A3" -v 1 -d "0423a132000000000000000000000000000000000000000000000000000000000000002a" ``` We then need to retrieve the smart account address. ```sh ./smart_accounts.py personal-account --from-env print ``` With the address, we can go to the [Flare faucet](https://faucet.flare.network/coston2) and request C2FLR for the smart account address. We can also do this through the CLI. ```sh ./smart_accounts.py personal-account --from-env faucet ``` Afterwards, we can run the following command again. This time, it works. ```sh ./smart_accounts.py bridge custom -a "0x296432C15504Ed465fAce11E54Ce4aac50cCd8A3" -v 1 -d "0423a132000000000000000000000000000000000000000000000000000000000000002a" ```
Expected output The CLI prompts the user to press "enter" once in order to continue to the minting. The number of `.` symbols, as well as XRPL transaction hashes are expected not to match the ones in this example. ``` sent instruction on underlying: 934945B8142E5CA10B2027B17364D587C483C61F562E2B28EF251EF3F9D0A6E5 https://testnet.xrpl.org/transactions/934945B8142E5CA10B2027B17364D587C483C61F562E2B28EF251EF3F9D0A6E5/detailed waiting to bridge........... BRIDGED FOR 0xF4CC61CCCDA8b792EAA112455D9DBc4bcb9AA9A0 successfully bridged in tx 0x0b889937eeb43b660ffe44b23c149a540af41c8a04ae656481a15a9ffe310953 https://coston2-explorer.flare.network/tx/0x0b889937eeb43b660ffe44b23c149a540af41c8a04ae656481a15a9ffe310953?tab=logs ```
### Mocking We can speed up the process, as well as simplify it, by using the Flare Smart Accounts CLI. First, we need to create a mock account, which we do with the command. This will only work if our Flare address has sufficient funds. ```sh ./smart_accounts.py debug mock-create-fund --seed "mockAccount" --value 1 ``` Here, we arbitrarily chose the name `mockAccount` as the account address. Behind the scenes, the string `mockAccount` will be concatenated with our Flare address. You can check the associated account with the command: ```sh ./smart_accounts.py debug mock-print -s ``` Then, we execute the custom instructions. We use the string `mockAccount` as the seed. ```sh ./smart_accounts.py debug mock-custom -s "mockAccount" -a "0x296432C15504Ed465fAce11E54Ce4aac50cCd8A3" -v 1 -d "0423a132000000000000000000000000000000000000000000000000000000000000002a" ``` --- ## State Lookup In this guide, you will learn how to get the following using the Viem TypeScript library: - the Flare address that corresponds to an XRPL account - whether a Flare address is a smart account - the total value of FXRP that belongs to a personal account tied to an XRPL address - a list of all the registered vaults - the total value of FXRP of a personal account, in each of the registered vaults - a list of all the registered agent vaults The code example is available on the [Flare Foundation GitHub](https://github.com/flare-foundation/flare-viem-starter). {StateLookupScript}
Expected output The following is an example of the expected output. The values will differ slightly, but the output should follow the same general format. The personal account address is tied to the XRPL address obtained from the XRPL secret value in the `.env` file. {/* TODO:(Nik) link to upshift and mint guides */} ```bash Operator XRPL addresses: [ 'rEyj8nsHLdgt79KJWzXR5BgF7ZbaohbXwq' ] Personal account address: 0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F Personal account FXRP balance: 0n Vaults: [ { id: 2n, address: '0x2C5f203CAd22f3d351912e634dDd0f93C6D503d9', type: 2 }, { id: 1n, address: '0x9BbE85a672Dd2fE2197516656FB2dC76c974954d', type: 1 } ] Vault 2 balance: 20000000n Vault 1 balance: 0n Agent vaults: [ { id: 1n, address: '0x55c815260cBE6c45Fe5bFe5FF32E3C7D746f14dC' } ] ```
Let us examine each of the helper functions in more detail. First of all, we need to define a [Viem public client](https://viem.sh/docs/clients/public). Since the two Flare mainnets and the two testnets are already registered with Viem, we simply need to import them. Then, we create a basic public client with the HTTP transporter. ```typescript export const publicClient = createPublicClient({ chain: flareTestnet, transport: http(), }); ``` With that, we can look at the actual functions. All of them rely primarily on the `readContract` function of the Viem `publicClient` to call read functions of contracts on the Flare chain. ## Operator's XRPL addresses {/* TODO:(Nik) update the sentence about in development */} The `getOperatorXrplAddresses` calls the `getXrplProviderWallets` function of the `MasterAccountController` contract. The function returns an array of XRPL addresses; these are all the registered operator XRPL addresses. ```typescript export async function getOperatorXrplAddresses() { const result = await publicClient.readContract({ address: await getMasterAccountControllerAddress(), abi: coston2.iMasterAccountControllerAbi, functionName: "getXrplProviderWallets", args: [], }); return result as string[]; } ``` We define a helper function for retrieving the address of the `MasterAccountController` contract from the `FlareContractsRegistry`. ```typescript export async function getMasterAccountControllerAddress(): Promise
{ return getContractAddressByName("MasterAccountController"); } ``` With the mainnet launch of the Flare smart accounts, the `MasterAccountController` has been added to the list of official Flare contracts. Its address can be obtained from the `FlareContractRegistry` contract by calling its `getContractAddressByName` function. ```typescript export async function getContractAddressByName(name: string) { const contractAddress = await publicClient.readContract({ address: FLARE_CONTRACT_REGISTRY_ADDRESS, abi: coston2.iFlareContractRegistryAbi, functionName: "getContractAddressByName", args: [name], }); return contractAddress; } ``` ## Checking if a Flare address is a smart account A Flare address is a smart account (PersonalAccount) if calling the `xrplOwner()` function on it returns an XRPL account using the [`IPersonalAccount`](/smart-accounts/reference/IPersonalAccount) interface. The `PersonalAccount` contract exposes the `xrplOwner()` function — the XRPL address that controls it. If the call succeeds and returns a non-empty and valid XRPL address, the address is a smart account. ```typescript export async function getXrplAccountForAddress( evmAddress: Address, ): Promise<`0x${string}`> { const xrplOwner = await publicClient.readContract({ address: evmAddress, abi: coston2.iPersonalAccountAbi, functionName: "xrplOwner", args: [], }); return xrplOwner && xrplOwner.length > 0 ? evmAddress : "0x0000000000000000000000000000000000000000"; } export async function isSmartAccount(evmAddress: Address): Promise { const smartAccountAddress = await getXrplAccountForAddress(evmAddress); return smartAccountAddress !== "0x0000000000000000000000000000000000000000"; } ``` ## Personal account of an XRPL address There are many situations when it is important or even necessary to know the address of the `PersonalAccount` of an XRPL address; like monitoring different emitted events and retrieving user balances. The `getPersonalAccountAddress` function retrieves the address of the `PersonalAccount` contract, belonging to the given XRPL address. It calls the `getPersonalAccount` function of the `MasterAccountController` contract, with the XRPL address as the function argument. ```typescript export async function getPersonalAccountAddress(xrplAddress: string) { const personalAccountAddress = await publicClient.readContract({ address: await getMasterAccountControllerAddress(), abi: coston2.iMasterAccountControllerAbi, functionName: "getPersonalAccount", args: [xrplAddress], }); return personalAccountAddress; } ``` ## Account's FXRP balance The `getFxrpBalance` function looks up the current FXRP balance of the given address. It calls the `balanceOf` function, which is a generic ERC-20 function of the FXRP token. ```typescript export async function getFxrpBalance(address: Address) { const fxrpAddress = await getFxrpAddress(); const fxrpBalance = await publicClient.readContract({ address: fxrpAddress, abi: erc20Abi, functionName: "balanceOf", args: [address], }); return fxrpBalance; } ``` The FXRP token address is obtained from the `AssetManagerFXRP` contract by calling its `fAsset` function. ```typescript export async function getFxrpAddress(): Promise
{ const assetManagerAddress = await getAssetManagerFXRPAddress(); const fxrpAddress = await publicClient.readContract({ address: assetManagerAddress, abi: coston2.iAssetManagerAbi, functionName: "fAsset", }); return fxrpAddress; } ``` The `AssetManagerFXRP` contract is one of the contracts registered with the `FlareContractRegistry` contract. This allows us to read the `AssetManagerFXRP` contract's address from the latter. ```typescript export async function getAssetManagerFXRPAddress(): Promise
{ const assetManagerAddress = await getContractAddressByName("AssetManagerFXRP"); return assetManagerAddress; } ``` ## Vaults and vault balances Certain instructions require the user to specify a vault ID. The user should also be able to check their share of each vault to which they have committed their funds. That is why it is important that we know how to retrieve those values. To get the array of registered vaults, we define two helper types. The `getVaults` function of the `MasterAccountController` contract returns an array of three arrays; the first one represents the vault IDs, the second their addresses, and the third their types. The vaults are either of the Firelight (`1`) or the Upshift (`2`) type. The `GetVaultsReturnType` type allows us to properly interpret that data. The `getVaults` function calls the function of the `MasterAccountController` contract with the same name. It iterates over the three sub-arrays and constructs a new array of the `Vault` type. ```typescript export type Vault = { id: bigint; address: Address; type: number; }; export type GetVaultsReturnType = [bigint[], string[], number[]]; export async function getVaults(): Promise { const _vaults = (await publicClient.readContract({ address: await getMasterAccountControllerAddress(), abi: coston2.iMasterAccountControllerAbi, functionName: "getVaults", args: [], })) as GetVaultsReturnType; const length = _vaults[0].length; if (length === 0) { return []; } const vaults = new Array(length) as Vault[]; _vaults[0].forEach((id, index) => { vaults[index] = { id, address: _vaults[1][index]! as Address, type: _vaults[2][index]!, }; }); return vaults; } ``` On the testnet, the vaults are mocks. They are straightforward ERC-4626 implementations. To get the vault balance of a Flare address, we call the ERC-46426 `balanceOf` function on the selected vault, with the account address as the argument. ```typescript async function getVaultBalance(vaultAddress: Address, accountAddress: Address) { const vaultBalance = await publicClient.readContract({ address: vaultAddress, abi: erc4626Abi, functionName: "balanceOf", args: [accountAddress], }); return vaultBalance; } ``` ## Agent vaults Smart account instructions, which include a minting step (`FXRPCollateralReservation`, `FirelightCollateralReservationAndDeposit`, and `UpshiftCollateralReservationAndDeposit`), require the user to specify the ID of the agent vault they wish to use. We obtain the list of all the registered agent vaults in a similar way to how we do for the deposit vaults. ```typescript export type AgentVault = { id: bigint; address: Address; }; export type GetAgentVaultsReturnType = [bigint[], string[]]; export async function getAgentVaults(): Promise { const _vaults = await publicClient.readContract({ address: await getMasterAccountControllerAddress(), abi: coston2.iMasterAccountControllerAbi, functionName: "getAgentVaults", args: [], }); const length = _vaults[0].length; if (length === 0) { return []; } const vaults = new Array(length) as AgentVault[]; _vaults[0].forEach((id, index) => { vaults[index] = { id, address: _vaults[1][index]!, }; }); return vaults; } ``` ## Video Tutorial --- ## Custom Instruction(Typescript-viem) The [Custom Instruction overview](/smart-accounts/custom-instruction) explains how Flare smart accounts replay an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) `PackedUserOperation` carried in an XRPL `Payment` memo. This guide walks through a TypeScript script that builds the user operation with Viem library, sends a payment transaction with the user operation on XRPL, and waits for the [`UserOperationExecuted`](/smart-accounts/reference/IMasterAccountController#useroperationexecuted) event on Flare. The script executes three calls on three example smart contracts on the Flare blockchain. Because the XRPL memo field is capped at roughly `1024` bytes, the calls are split into two user operations sent in sequence. A prerequisite for the user operation to be executed is that the personal account holds enough native tokens to cover the call values. The script calls forward a total of `2` C2FLR (Coston2 Flare native tokens), so fund the personal account from the [Flare faucet](https://faucet.flare.network/coston2) before running it. The [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts#personal-account-of-an-xrpl-address) shows how to derive the personal account address. The full code is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/custom-instructions.ts). ## Contracts The first contract is a `Checkpoint` that counts how many times each user has called its `passCheckpoint` function. It is useless as anything other than an example. The first call of our user operation will be to call the `passCheckpoint` function of this contract, deployed at the address [`0xEE6D54382aA623f4D16e856193f5f8384E487002`](https://coston2-explorer.flare.network/address/0xEE6D54382aA623f4D16e856193f5f8384E487002?tab=contract). ```Solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract Checkpoint { mapping(address => uint256) public numberOfPasses; function passCheckpoint() public payable { ++numberOfPasses[msg.sender]; } } ``` The second contract is a `PiggyBank`, and is a degree more useful. It allows a user to `deposit` FLR into it, and `withdraw` it all at once. Our second call will be to deposit `1` FLR into the `PiggyBank` contract at the address [`0x42Ccd4F0aB1C6Fa36BfA37C9e30c4DC4DD94dE42`](https://coston2-explorer.flare.network/address/0x42Ccd4F0aB1C6Fa36BfA37C9e30c4DC4DD94dE42?tab=contract). ```Solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; contract PiggyBank { mapping(address => uint256) public balances; function deposit() public payable { balances[msg.sender] += msg.value; } function withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0); delete balances[msg.sender]; (bool success, ) = payable(msg.sender).call{ value: amount }(""); require(success); } } ``` The third and last contract is a `NoticeBoard`. It allows users to `pinNotice`. The value they send in this transaction will determine how long the notice stays up. As the contract is set up, `1` FLR gets you `30` days, with fractions allowed. The last call our personal account will perform is to pin a notice with the message `Hello World!` to the `NoticeBoard` at the address [`0x59D57652BF4F6d97a6e555800b3920Bd775661Dc`](https://coston2-explorer.flare.network/address/0x59D57652BF4F6d97a6e555800b3920Bd775661Dc?tab=contract) for `30` days (which means we attach a value of `1` FLR). ```Solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; struct Notice { uint256 expirationTimestamp; string message; } contract NoticeBoard { uint256 public constant THIRTY_DAYS = 2592000; mapping(address => Notice) public notices; mapping(address => bool) public existingClients; address[] public clients; function pinNotice(string memory message) public payable { require(msg.value > 0); uint256 duration = THIRTY_DAYS * (msg.value / 1 ether); uint256 expirationTimestamp = block.timestamp + duration; notices[msg.sender] = Notice(expirationTimestamp, message); if (!existingClients[msg.sender]) { clients.push(msg.sender); existingClients[msg.sender] = true; } } function getNotices() public view returns (Notice[] memory) { Notice[] memory _notices = new Notice[](clients.length); for (uint256 i = 0; i < clients.length; ++i) { Notice memory notice = notices[clients[i]]; if (notice.expirationTimestamp > block.timestamp) { _notices[i] = notice; } } return _notices; } } ``` The contract also performs additional checks to ensure each client is only added to the array once, and that `getNotices` returns only notices that have not yet expired. ## Building the call array Each entry in the user operation is a [`Call`](/smart-accounts/reference/IPersonalAccount#call) struct with three fields: - `target` — the address of the contract to call; - `value` — the amount of FLR to send; - `data` — the function calldata (function selector and parameter encoding). ```typescript export type Call = { target: Address; value: bigint; data: `0x${string}`; }; ``` We use Viem's [`encodeFunctionData`](https://viem.sh/docs/contract/encodeFunctionData) to build each `data` field from the contract ABI, function name, and arguments. XRPL caps the memo at roughly `1024` bytes. The `pinNotice` call carries a string argument that, together with the other two calls, exceeds that limit, so the script splits the work into two user operations: ```typescript const checkpointAndDepositCalls: Call[] = [ { target: checkpointAddress, value: BigInt(0), data: encodeFunctionData({ abi: checkpointAbi, functionName: "passCheckpoint", args: [], }), }, { target: piggyBankAddress, value: BigInt(depositAmount), data: encodeFunctionData({ abi: piggyBankAbi, functionName: "deposit", args: [], }), }, ]; const pinNoticeCalls: Call[] = [ { target: noticeBoardAddress, value: BigInt(pinNoticeAmount), data: encodeFunctionData({ abi: noticeBoardAbi, functionName: "pinNotice", args: [pinNoticeMessage], }), }, ]; ``` ## Personal account and nonce The personal account address is deterministic, so we can fetch it before the account is even deployed by calling [`getPersonalAccount`](/smart-accounts/reference/IMasterAccountController#getpersonalaccount) on the [`MasterAccountController`](/smart-accounts/reference/IMasterAccountController). Before encoding the user operation, we also need the current nonce from [`getNonce`](/smart-accounts/reference/IMasterAccountController#getnonce). Each successful execution increments it, so passing an invalid value reverts to [`InvalidNonce`](/smart-accounts/reference/IMasterAccountController#invalidnonce). ```typescript export async function getNonce(personalAccount: Address): Promise { return publicClient.readContract({ address: await getMasterAccountControllerAddress(), abi: iMemoInstructionsFacetAbi, functionName: "getNonce", args: [personalAccount], }) as Promise; } ``` ## Encoding the user operation The XRPL memo carries a 10-byte header followed by an ABI-encoded [`PackedUserOperation`](https://eips.ethereum.org/EIPS/eip-4337#useroperation): - 1st byte: custom instruction command ID `0xff` - 2nd byte: `walletId` — a one-byte wallet identifier assigned by Flare; we use `0` - bytes 3-10: `executorFeeUBA` as an 8-byte big-endian unsigned integer Only three fields of the `PackedUserOperation` are validated on chain: `sender` must equal the personal account address, `nonce` must equal the current nonce, and `callData` must be the encoded `executeUserOp(calls)` call. The rest can be left empty. ```typescript export function encodeExecuteUserOpMemo({ calls, walletId, executorFeeUBA, sender, nonce, }: { calls: Call[]; walletId: number; executorFeeUBA: bigint; sender: Address; nonce: bigint; }): `0x${string}` { const callData = encodeFunctionData({ abi: coston2.iPersonalAccountAbi, functionName: "executeUserOp", args: [calls], }); const encodedUserOp = encodeAbiParameters( [PACKED_USER_OPERATION_TUPLE], [ { sender, nonce, initCode: "0x", callData, accountGasLimits: ZERO_BYTES32, preVerificationGas: 0n, gasFees: ZERO_BYTES32, paymasterAndData: "0x", signature: "0x", }, ], ); // 10-byte header: 0xFF | walletId (1B) | executorFee (8B, big-endian) const header = concatHex([ "0xff", toHex(walletId, { size: 1 }), toHex(executorFeeUBA, { size: 8 }), ]); return concatHex([header, encodedUserOp]); } ``` ## Computing the XRPL payment amount A custom instruction rides on top of an FAssets direct minting payment, so the XRPL transfer must cover the net mint amount plus the minting fee and the executor fee, as read from the `AssetManagerFXRP` contract. For a memo-only transaction (no FXRP mint), the net amount is `0`, but the fees still apply. ```typescript export async function computeDirectMintingPaymentAmountXrp({ netMintAmountXrp, }: { netMintAmountXrp: number; }): Promise { const assetManagerAddress = await getContractAddressByName("AssetManagerFXRP"); const [executorFeeUBA, feeBIPS, minimumFeeUBA] = await Promise.all([ publicClient.readContract({ address: assetManagerAddress, abi: coston2.iDirectMintingSettingsAbi, functionName: "getDirectMintingExecutorFeeUBA", }), publicClient.readContract({ address: assetManagerAddress, abi: coston2.iDirectMintingSettingsAbi, functionName: "getDirectMintingFeeBIPS", }), publicClient.readContract({ address: assetManagerAddress, abi: coston2.iDirectMintingSettingsAbi, functionName: "getDirectMintingMinimumFeeUBA", }), ]); const netMintUBA = BigInt(xrpToDrops(netMintAmountXrp)); const proportionalFeeUBA = (netMintUBA * feeBIPS) / 10_000n; const mintingFeeUBA = proportionalFeeUBA > minimumFeeUBA ? proportionalFeeUBA : minimumFeeUBA; const totalUBA = netMintUBA + mintingFeeUBA + executorFeeUBA; return Number(dropsToXrp(totalUBA.toString())); } ``` The first user operation in the script also mints `10` FXRP as a side effect, so its payment amount includes the net mint amount. The second user operation carries only a memo, so its payment covers only fees. ## Sending the user operation The XRPL `Payment` is sent to the FAssets **direct minting payment address** read from the `AssetManagerFXRP` contract — not to an operator wallet. The encoded user operation is attached as a memo, with the `0x` prefix removed. :::warning No destination tags XRPL payments targeting smart accounts must not use a destination tag. A destination tag forces the FAssets direct minting flow to credit the tag holder, which could let an unrelated party front-run the user's operation. ::: ```typescript export async function sendMemoFieldInstruction({ label, calls, amountXrp, personalAccount, xrplClient, xrplWallet, }: { label: string; calls: Call[]; amountXrp: number; personalAccount: Address; xrplClient: Client; xrplWallet: Wallet; }) { const nonce = await getNonce(personalAccount); const memoData = encodeExecuteUserOpMemo({ calls, walletId: 0, executorFeeUBA: 0n, sender: personalAccount, nonce, }); const coreVaultXrplAddress = await getDirectMintingPaymentAddress(); const transaction = await sendXrplPayment({ destination: coreVaultXrplAddress, amount: amountXrp, memos: [{ Memo: { MemoData: memoData.slice(2) } }], wallet: xrplWallet, client: xrplClient, }); const event = await waitForUserOperationExecuted({ personalAccount, nonce }); return event; } ``` The `xrplClient` and `xrplWallet` are the `Client` and `Wallet` classes from the `xrpl` library, initialized from the `.env` file: ```typescript const xrplClient = new Client(process.env.XRPL_TESTNET_RPC_URL!); const xrplWallet = Wallet.fromSeed(process.env.XRPL_SEED!); ``` ## Waiting for execution Once the operator bridges the instruction from XRPL to Flare, the personal account runs `executeUserOp` and the `MasterAccountController` emits [`UserOperationExecuted`](/smart-accounts/reference/IMasterAccountController#useroperationexecuted) event. We watch for that event with Viem's [`watchContractEvent`](https://viem.sh/docs/contract/watchContractEvent#watchcontractevent) function, filtering on the personal account and the nonce we submitted: ```typescript export async function waitForUserOperationExecuted({ personalAccount, nonce, }: { personalAccount: Address; nonce: bigint; }): Promise { const masterAccountControllerAddress = await getMasterAccountControllerAddress(); return new Promise((resolve) => { const unwatch = publicClient.watchContractEvent({ address: masterAccountControllerAddress, abi: iMemoInstructionsFacetAbi, eventName: "UserOperationExecuted", onLogs: (logs) => { for (const log of logs) { const typedLog = log as UserOperationExecutedEventType; if ( typedLog.args.personalAccount.toLowerCase() !== personalAccount.toLowerCase() || typedLog.args.nonce !== nonce ) { continue; } unwatch(); resolve(typedLog); return; } }, }); }); } ``` The bridging is handled by the [Flare Data Connector](/fdc/overview), which caps the round trip at 180 seconds. If any inner call reverts, the whole user operation reverts with [`CallFailed`](/smart-accounts/reference/IPersonalAccount#callfailed) and the nonce does not increment. The FXRP transfer, however, is performed before the memo is decoded, so the mint succeeds even when the user operation reverts — see [`DirectMintingExecuted`](/smart-accounts/reference/IMasterAccountController#directmintingexecuted). ## Full script The repository with the example is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter). Helper functions live in the `src/utils` directory. {CustomInstructionsScript} ## Expected output ```bash Personal account address: 0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F Payment amount (XRP, net mint + fees): 10.0011 Memo-only amount (XRP, fees only): 0.0011 [checkpoint-and-deposit] calls: [ { target: '0xEE6D54382aA623f4D16e856193f5f8384E487002', value: 0n, data: '0x80abd133' }, { target: '0x42Ccd4F0aB1C6Fa36BfA37C9e30c4DC4DD94dE42', value: 1000000000000000000n, data: '0xd0e30db0' } ] [checkpoint-and-deposit] current nonce: 0n [checkpoint-and-deposit] XRPL transaction hash: 03165B82E4B7BB168AF7B217C9EC833896E968C796CA8BF8D2E66879ED311909 [checkpoint-and-deposit] UserOperationExecuted event: { eventName: 'UserOperationExecuted', args: { personalAccount: '0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F', nonce: 0n }, ... } [pin-notice] calls: [ { target: '0x59D57652BF4F6d97a6e555800b3920Bd775661Dc', value: 1000000000000000000n, data: '0x28d106b20000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000' } ] [pin-notice] current nonce: 1n [pin-notice] XRPL transaction hash: 9F1A4D7E2B83C5C0A6F4E1D8B7C9A2E5F6D3B4C1E0F7A8B9C2D3E4F5A6B7C8D9 [pin-notice] UserOperationExecuted event: { eventName: 'UserOperationExecuted', args: { personalAccount: '0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F', nonce: 1n }, ... } ``` :::tip What's next? To continue your Flare Smart Accounts development journey, you can: - Get the smart account state using the [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts). - Explore the [Custom Instruction](/smart-accounts/custom-instruction) overview. - Dig into the `IMasterAccountController` [reference](/smart-accounts/reference/IMasterAccountController). ::: --- ## Cross-Chain Mint In this guide, you will learn how to mint FXRP from native XRP on the XRPL and bridge it to Sepolia in a single end-to-end flow. The script combines two operations into one user journey: - mint FXRP on Flare from a payment made on the XRPL by the user's personal account. - bridge the minted FXRP to Sepolia using a LayerZero OFT, fronted by the `FxrpLzBridgeShim` helper contract. - wait for the `OFTReceived` event on Sepolia to confirm that the FXRP has arrived. The script defaults to a public deployment of the bridge shim on Coston2 at [`0x525CCe1C6d053B0e7f41A2011B536aA992200Be0`](https://coston2-explorer.flare.network/address/0x525CCe1C6d053B0e7f41A2011B536aA992200Be0?tab=contract); override it via the `FXRP_LZ_BRIDGE_SHIM` environment variable only if you have redeployed the contract yourself. The shim resolves the FXRP token address on-chain from the [`AssetManagerFXRP`](/fassets/reference/IAssetManager) entry in the [`FlareContractRegistry`](https://dev.flare.network/network/guides/flare-contracts-registry.md), so it does not need to be passed at deploy time. For Flare-to-Sepolia, the only route-specific params baked in at deploy are: - `oftAdapter = 0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639`, - `dstEid = 40161` (the Sepolia LayerZero V2 endpoint ID), - `executorGas = 200000`. A prerequisite for the script to work is a funded personal account on Flare (the C2FLR is used to pay the LayerZero native fee) and a funded XRPL testnet wallet that will originate the XRPL payment. The [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts#personal-account-of-an-xrpl-address) explains how to obtain the personal account address from the XRPL address. Once known, the personal account can be funded using the [Flare faucet](https://faucet.flare.network/coston2). The full code showcased in this guide is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter). :::info The code in this guide is set up for the Coston2 testnet and the Sepolia testnet. Despite that, we will refer to the Flare-side network as Flare and its currency as FLR instead of Coston2 and C2FLR. Likewise, we will refer to the Sepolia testnet's currency as ETH instead of SETH. ::: ## Setup The script uses three clients: a Flare public client, a Sepolia public client, and an XRPL client. The Flare client is the same one introduced in the [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts); a second Viem public client is added for Sepolia, plus a wallet client and a signing account for write operations. ```typescript title="src/utils/client.ts" export const publicClient = createPublicClient({ chain: flareTestnet, transport: http(), }); export const account = privateKeyToAccount( process.env.PRIVATE_KEY as `0x${string}`, ); export const sepoliaPublicClient = createPublicClient({ chain: sepolia, transport: http(process.env.SEPOLIA_RPC_URL), }); export const sepoliaWalletClient = createWalletClient({ chain: sepolia, transport: http(process.env.SEPOLIA_RPC_URL), }); ``` The XRPL `Client` and `Wallet` are imported from the [`xrpl`](https://js.xrpl.org) library and initialised from environment variables. ```typescript title="src/cross-chain-mint.ts" const xrplClient = new Client(process.env.XRPL_TESTNET_RPC_URL!); const xrplWallet = Wallet.fromSeed(process.env.XRPL_SEED!); const recipient = account.address; ``` The recipient of the bridged FXRP on Sepolia is the main externally owned account (EOA) loaded from the wallet client, not the personal account on Flare. The personal account is only used as the intermediary that performs the mint and the bridge call. Before sending anything, the script reads a few values in parallel: - the personal account address tied to the XRPL wallet, - the FXRP token address on Flare, - the FXRP decimals, - two XRPL payment amounts (one that covers the mint and one that only covers the fixed memo-field fee). ```typescript title="src/cross-chain-mint.ts" const [ personalAccount, fxrpAddress, fxrpDecimals, paymentAmountXrp, memoOnlyAmountXrp, ] = await Promise.all([ getPersonalAccountAddress(xrplWallet.address), getFxrpAddress(), getFxrpDecimals(), computeDirectMintingPaymentAmountXrp({ netMintAmountXrp: CONFIG.FXRP_MINT_AMOUNT_XRP, }), computeDirectMintingPaymentAmountXrp({ netMintAmountXrp: 0 }), ]); ``` The `getPersonalAccountAddress` and `getFxrpAddress` functions are covered in the [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts). The `getFxrpDecimals` function is a thin ERC-20 read on the FXRP token: ```typescript title="src/utils/fassets.ts" export async function getFxrpDecimals() { const fxrpAddress = await getFxrpAddress(); const decimals = await publicClient.readContract({ address: fxrpAddress, abi: erc20Abi, functionName: "decimals", args: [], }); return decimals; } ``` The `computeDirectMintingPaymentAmountXrp` function is a helper that computes the XRP amount required for a [direct minting](/fassets/direct-minting) payment. It reads three values from the FXRP `AssetManager` — the per-operation executor fee, the proportional minting fee (in BIPS), and the minimum minting fee — and adds them on top of the requested net mint amount. ```typescript title="src/utils/fassets.ts" export async function computeDirectMintingPaymentAmountXrp({ netMintAmountXrp, }: { netMintAmountXrp: number; }): Promise { const assetManagerAddress = await getContractAddressByName("AssetManagerFXRP"); const [executorFeeUBA, feeBIPS, minimumFeeUBA] = await Promise.all([ publicClient.readContract({ address: assetManagerAddress, abi: coston2.iDirectMintingSettingsAbi, functionName: "getDirectMintingExecutorFeeUBA", }), publicClient.readContract({ address: assetManagerAddress, abi: coston2.iDirectMintingSettingsAbi, functionName: "getDirectMintingFeeBIPS", }), publicClient.readContract({ address: assetManagerAddress, abi: coston2.iDirectMintingSettingsAbi, functionName: "getDirectMintingMinimumFeeUBA", }), ]); const netMintUBA = BigInt(xrpToDrops(netMintAmountXrp)); const proportionalFeeUBA = (netMintUBA * feeBIPS) / 10_000n; const mintingFeeUBA = proportionalFeeUBA > minimumFeeUBA ? proportionalFeeUBA : minimumFeeUBA; const totalUBA = netMintUBA + mintingFeeUBA + executorFeeUBA; return Number(dropsToXrp(totalUBA.toString())); } ``` The first call from the `Promise.all` computes the XRP amount that, once paid to the personal account on XRPL, will result in `FXRP_MINT_AMOUNT_XRP` worth of FXRP being minted on Flare net of all fees. The second call (with `netMintAmountXrp: 0`) returns the much smaller XRP amount needed when no minting happens — only the executor fee and the minimum minting fee — which is what we attach to the second memo-field instruction. ## Quoting the LayerZero fee The `FxrpLzBridgeShim` contract exposes a view function `quote(amount, recipient)` that returns the native fee (in FLR) that the LayerZero send will charge. The fee must be attached to the `bridge` call as `msg.value`. ```typescript title="src/cross-chain-mint.ts" const nativeFee = await publicClient.readContract({ address: shim, abi: fxrpLzBridgeShimAbi, functionName: "quote", args: [amountToBridge, recipient], }); ``` The `amountToBridge` parameter is the FXRP amount in base units, derived from the XRP-denominated mint amount via `xrpToDrops`. ## Encoding the approve and bridge calls The personal account holds the freshly minted FXRP, so it must approve the shim contract for that amount and then call `bridge`. Each call is encoded into a `Call` struct that the smart account memo-field instruction can execute. ```typescript title="src/utils/smart-accounts.ts" export type Call = { target: Address; value: bigint; data: `0x${string}`; }; ``` The fields mirror the `Call` struct the `PersonalAccount` contract takes in `executeUserOp`: the target address, the native value to forward, and the encoded calldata. ```typescript title="src/cross-chain-mint.ts" const approveShimCalls: Call[] = [ { target: fxrpAddress, value: 0n, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [shim, amountToBridge], }), }, ]; const bridgeCalls: Call[] = [ { target: shim, value: nativeFee, data: encodeFunctionData({ abi: fxrpLzBridgeShimAbi, functionName: "bridge", args: [amountToBridge, recipient], }), }, ]; ``` Note that the `bridge` call carries `value: nativeFee` — the FLR that pays for the cross-chain message. The shim passes `msg.sender` — i.e. the personal account that called `bridge` — to the OFT Adapter as the refund address, so any unused native fee is returned to the personal account automatically. ## Sending the memo-field instructions The encoded calls are dispatched by issuing an XRPL **direct mint payment** to the FXRP core vault, with an ABI-encoded `UserOperation` placed in the memo field. The XRP amount in that payment is what mints FXRP on Flare; the memo is what tells the personal account what to do with it. The FAssets executor picks the payment up off the XRPL, proves it via the [Flare Data Connector](https://dev.flare.network/fdc/attestation-types/xrp-payment.md), and delivers the memo to the `MasterAccountController` contract — which in turn dispatches the `UserOperation` to the personal account on Flare. The `sendMemoFieldInstruction` helper bundles the four steps required: read the current nonce, encode the memo, submit the XRPL payment, and wait for the on-chain event that confirms execution. ```typescript title="src/utils/smart-accounts.ts" export async function sendMemoFieldInstruction({ label, calls, amountXrp, personalAccount, xrplClient, xrplWallet, }: { label: string; calls: Call[]; amountXrp: number; personalAccount: Address; xrplClient: Client; xrplWallet: Wallet; }) { const nonce = await getNonce(personalAccount); const memoData = encodeExecuteUserOpMemo({ calls, walletId: 0, executorFeeUBA: 0n, sender: personalAccount, nonce, }); const coreVaultXrplAddress = await getDirectMintingPaymentAddress(); const transaction = await sendXrplPayment({ destination: coreVaultXrplAddress, amount: amountXrp, memos: [{ Memo: { MemoData: memoData.slice(2) } }], wallet: xrplWallet, client: xrplClient, }); const event = await waitForUserOperationExecuted({ personalAccount, nonce }); return event; } ``` The destination of the XRPL payment is resolved by `getDirectMintingPaymentAddress`, a thin helper that looks up the FXRP `AssetManager` from the `FlareContractRegistry` and reads [`directMintingPaymentAddress()`](/fassets/reference/IAssetManager#directmintingpaymentaddress) on it. The returned XRPL address is the core vault that the FAssets executor is listening to. ```typescript title="src/utils/smart-accounts.ts" export async function getDirectMintingPaymentAddress(): Promise { const assetManagerAddress = await getAssetManagerFXRPAddress(); return publicClient.readContract({ address: assetManagerAddress, abi: coston2.iDirectMintingAbi, functionName: "directMintingPaymentAddress", }); } ``` The XRP amount is what mints FXRP on Flare, and the memo carries a `UserOperation` that the personal account executes in the same on-chain transaction as the mint. Bundling the two into a single XRPL payment is what makes mint-and-act atomic on Flare without needing to register and dispatch a separate custom instruction. The nonce is read from the [`MasterAccountController`](/smart-accounts/reference/IMasterAccountController); it increments with every executed user operation and prevents replay. ```typescript title="src/utils/smart-accounts.ts" export async function getNonce(personalAccount: Address): Promise { return publicClient.readContract({ address: await getMasterAccountControllerAddress(), abi: iMemoInstructionsFacetAbi, functionName: "getNonce", args: [personalAccount], }) as Promise; } ``` The memo is built in `encodeExecuteUserOpMemo`. It first encodes the array of `Call` structs as the calldata for `PersonalAccount.executeUserOp`, then wraps that calldata in a `PackedUserOperation` tuple matching ERC-4337's layout (with empty `initCode`, `paymasterAndData`, and `signature` for memo-bridged operations), and finally prepends a 10-byte header. The header is `0xFF` (instruction ID for memo-bridged user operations) followed by the wallet identifier byte and an 8-byte executor fee in big-endian UBA. ```typescript title="src/utils/smart-accounts.ts" const PACKED_USER_OPERATION_TUPLE = { type: "tuple", components: [ { name: "sender", type: "address" }, { name: "nonce", type: "uint256" }, { name: "initCode", type: "bytes" }, { name: "callData", type: "bytes" }, { name: "accountGasLimits", type: "bytes32" }, { name: "preVerificationGas", type: "uint256" }, { name: "gasFees", type: "bytes32" }, { name: "paymasterAndData", type: "bytes" }, { name: "signature", type: "bytes" }, ], } as const; const ZERO_BYTES32 = ("0x" + "00".repeat(32)) as `0x${string}`; export function encodeExecuteUserOpMemo({ calls, walletId, executorFeeUBA, sender, nonce, }: { calls: Call[]; walletId: number; executorFeeUBA: bigint; sender: Address; nonce: bigint; }): `0x${string}` { const callData = encodeFunctionData({ abi: coston2.iPersonalAccountAbi, functionName: "executeUserOp", args: [calls], }); const encodedUserOp = encodeAbiParameters( [PACKED_USER_OPERATION_TUPLE], [ { sender, nonce, initCode: "0x", callData, accountGasLimits: ZERO_BYTES32, preVerificationGas: 0n, gasFees: ZERO_BYTES32, paymasterAndData: "0x", signature: "0x", }, ], ); const header = concatHex([ "0xff", toHex(walletId, { size: 1 }), toHex(executorFeeUBA, { size: 8 }), ]); return concatHex([header, encodedUserOp]); } ``` After the XRPL payment is in, `waitForUserOperationExecuted` watches the `MasterAccountController` for the `UserOperationExecuted` event whose `personalAccount` and `nonce` match the one we just submitted. ```typescript title="src/utils/smart-accounts.ts" export async function waitForUserOperationExecuted({ personalAccount, nonce, }: { personalAccount: Address; nonce: bigint; }): Promise { const masterAccountControllerAddress = await getMasterAccountControllerAddress(); return new Promise((resolve) => { const unwatch = publicClient.watchContractEvent({ address: masterAccountControllerAddress, abi: iMemoInstructionsFacetAbi, eventName: "UserOperationExecuted", onLogs: (logs) => { for (const log of logs) { const typedLog = log as UserOperationExecutedEventType; if ( typedLog.args.personalAccount.toLowerCase() !== personalAccount.toLowerCase() || typedLog.args.nonce !== nonce ) { continue; } unwatch(); resolve(typedLog); return; } }, }); }); } ``` With those helpers in place, the script splits the work into two memo-field instructions: ```typescript title="src/cross-chain-mint.ts" await sendMemoFieldInstruction({ label: "mint-and-approve-shim", calls: approveShimCalls, amountXrp: paymentAmountXrp, personalAccount, xrplClient, xrplWallet, }); const startSepoliaBlock = await sepoliaPublicClient.getBlockNumber(); const bridgeEvent = await sendMemoFieldInstruction({ label: "bridge", calls: bridgeCalls, amountXrp: memoOnlyAmountXrp, personalAccount, xrplClient, xrplWallet, }); ``` The first payment is the one that actually mints FXRP: the XRP arrives at the [Core Vault](/fassets/core-vault) on XRPL, the FAssets executor proves the [direct mint](/fassets/direct-minting) payment to Flare, and the `MasterAccountController` mints FXRP into the personal account and executes the embedded approve in the same on-chain transaction. The second payment carries no mint value (`memoOnlyAmountXrp` only covers the protocol fee for the memo) and triggers the bridge call that hands the FXRP to LayerZero. :::info The split exists because the XRPL memo field is capped at roughly 1024 bytes. A combined approve + bridge `UserOperation` encodes to about 1066 bytes — 42 bytes over the cap — so the two calls must be sent independently. ::: The Sepolia block number is captured between the two instructions, so the subsequent event poll has a tight lower bound. ## Waiting for arrival on Sepolia Once the bridge memo has been executed and LayerZero has accepted the message, the script tracks the LayerZero scanner link and then polls Sepolia for the `OFTReceived` event on the [FXRP OFT](/fxrp/oft) contract. ```typescript title="src/cross-chain-mint.ts" const arrivalEvent = await waitForOftReceivedOnSepolia({ oftAddress: sepoliaOft, toAddress: recipient, fromBlock: startSepoliaBlock, }); ``` The `waitForOftReceivedOnSepolia` helper is defined in the same file as the main flow. It queries `sepoliaPublicClient.getContractEvents` every 10 seconds for up to 10 minutes, filtering on the recipient address. ```typescript title="src/cross-chain-mint.ts" const SEPOLIA_ARRIVAL_TIMEOUT_MS = 10 * 60 * 1000; const SEPOLIA_ARRIVAL_POLL_INTERVAL_MS = 10_000; async function waitForOftReceivedOnSepolia({ oftAddress, toAddress, fromBlock, }: { oftAddress: Address; toAddress: Address; fromBlock: bigint; }) { const deadline = Date.now() + SEPOLIA_ARRIVAL_TIMEOUT_MS; while (Date.now() < deadline) { const logs = await sepoliaPublicClient.getContractEvents({ address: oftAddress, abi: fxrpOftAbi, eventName: "OFTReceived", args: { toAddress }, fromBlock, strict: true, }); if (logs.length > 0) { return logs[0]!; } await new Promise((resolve) => setTimeout(resolve, SEPOLIA_ARRIVAL_POLL_INTERVAL_MS), ); } throw new Error( `OFTReceived event not observed on Sepolia within ${SEPOLIA_ARRIVAL_TIMEOUT_MS}ms`, ); } ``` Cross-chain delivery typically completes well within that window, but the timeout is generous because LayerZero's Sepolia executor occasionally batches messages. ## Full script The repository with the above example is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter). In the example repository, certain helpers are isolated into separate files in the `src/utils` directory. {CrossChainMintScript} ## Expected output ```bash Personal account: 0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F FXRP token: 0x0b6A3645c240605887a5532109323A3E12273dc7 Bridge shim: 0x525CCe1C6d053B0e7f41A2011B536aA992200Be0 Cross-chain mint details: From (XRPL): rPdLcCkSJzLvURM2vV3bCWwXBgT7FyJojU Via (Coston2 personal account): 0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F To (Sepolia): 0xF5488132432118596fa13800B68df4C0fF25131d Net FXRP to mint & bridge: 10 FXRP XRPL payment amount (mint + fees): 10.2 XRP LayerZero native fee: 22.950824887834713257 C2FLR [mint-and-approve-shim] calls: [ { target: '0x0b6A3645c240605887a5532109323A3E12273dc7', value: 0n, data: '0x095ea7b3000000000000000000000000525cce1c6d053b0e7f41a2011b536aa992200be00000000000000000000000000000000000000000000000000000000000989680' } ] [mint-and-approve-shim] current nonce: 52n [mint-and-approve-shim] XRPL transaction hash: 96D1C1F07CBD8D471DB83C184056F16B3F9656E50F384586A295883662949E4F [mint-and-approve-shim] UserOperationExecuted event: { eventName: 'UserOperationExecuted', args: { personalAccount: '0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F', nonce: 52n }, address: '0x434936d47503353f06750db1a444dbdc5f0ad37c', topics: [ '0xf1fb8f9b365735a54cdafe3a27ffbad0a0cf1f35454f0c4c0c4dc68591d484fe', '0x000000000000000000000000fd2f0eb6b9fa4fe5bb1f7b26fee3c647ed103d9f' ], data: '0x0000000000000000000000000000000000000000000000000000000000000034', blockNumber: 30438058n, transactionHash: '0x3fef14baf0773b8f207809724409d93e3adc462bcd547171a4bddb4f5521df52', transactionIndex: 0, blockHash: '0x197214d48d2cfcd35b0b710342459a5761bf631072a0fcf4145aaf5be48fe721', logIndex: 6, removed: false, blockTimestamp: undefined } [bridge] calls: [ { target: '0x525CCe1C6d053B0e7f41A2011B536aA992200Be0', value: 22950824887834713257n, data: '0x9394d2e80000000000000000000000000000000000000000000000000000000000989680000000000000000000000000f5488132432118596fa13800b68df4c0ff25131d' } ] [bridge] current nonce: 53n [bridge] XRPL transaction hash: 014A8541410B6A419C1CEFA66588FD3769AC4EF00737ABC998D170991AB019CA [bridge] UserOperationExecuted event: { eventName: 'UserOperationExecuted', args: { personalAccount: '0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F', nonce: 53n }, address: '0x434936d47503353f06750db1a444dbdc5f0ad37c', topics: [ '0xf1fb8f9b365735a54cdafe3a27ffbad0a0cf1f35454f0c4c0c4dc68591d484fe', '0x000000000000000000000000fd2f0eb6b9fa4fe5bb1f7b26fee3c647ed103d9f' ], data: '0x0000000000000000000000000000000000000000000000000000000000000035', blockNumber: 30438109n, transactionHash: '0xab44459cb4f758e442008ce18262115273a03e40ad16553411cbfab1292bd56a', transactionIndex: 0, blockHash: '0x393535e7d19f64b8990050c0d72f8c66feb0402527df0216294e5353143a0dcd', logIndex: 14, removed: false, blockTimestamp: undefined } Track your cross-chain transaction: https://testnet.layerzeroscan.com/tx/0xab44459cb4f758e442008ce18262115273a03e40ad16553411cbfab1292bd56a Waiting for FXRP to arrive on Sepolia (this can take a few minutes)... FXRP arrived on Sepolia: Tx hash: 0xdaa60adf3cb24627dc71294b5e6921c14c1a5418c7ab2206cfc65fee9510497a Amount received: 10 FXRP Recipient: 0xF5488132432118596fa13800B68df4C0fF25131d ``` ## What's next - [Cross-Chain Redeem guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-ts) - [Cross-Chain Redeem to Tag guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-to-tag-ts) - [Direct Minting overview](/fassets/direct-minting) --- ## Cross-Chain Redeem In this guide, you will learn how to send FXRP from Sepolia back to Flare and trigger an automatic redemption to native XRP on the XRPL, all in a single LayerZero transaction. The flow is: - the user holds FXRP on Sepolia and calls `send` on the FXRP OFT with a compose message attached. - the message is delivered to a composer contract on Flare, which calls the `AssetManager` to redeem the FXRP for XRP. - the XRP is paid out by an agent to the XRPL address specified in the compose message — without a destination tag. A prerequisite for the script to work is a Sepolia externally owned account (EOA) that already holds FXRP and enough ETH to cover the LayerZero native fee. If you need to obtain FXRP on Sepolia first, see the [Cross-Chain Mint guide](/smart-accounts/guides/typescript-viem/cross-chain-mint-ts). The default composer address (`0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0`) can be overridden via the `COSTON2_COMPOSER` environment variable; the amount and destination are controlled by `SEND_LOTS` and `XRP_ADDRESS`. The full code showcased in this guide is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter). :::info The code in this guide is set up for the Coston2 and Sepolia testnets. Despite that, we will refer to the Flare-side network as Flare and its currency as FLR instead of Coston2 and C2FLR. Likewise, we will refer to the Sepolia testnet's currency as ETH instead of SETH. ::: ## Setup Because the script reads from and writes to two chains, it sets up Viem public and wallet clients for both Flare and Sepolia, plus a signing account loaded from `PRIVATE_KEY`. ```typescript title="src/utils/client.ts" export const publicClient = createPublicClient({ chain: flareTestnet, transport: http(), }); export const account = privateKeyToAccount( process.env.PRIVATE_KEY as `0x${string}`, ); export const sepoliaPublicClient = createPublicClient({ chain: sepolia, transport: http(process.env.SEPOLIA_RPC_URL), }); export const sepoliaWalletClient = createWalletClient({ chain: sepolia, transport: http(process.env.SEPOLIA_RPC_URL), }); ``` The Flare public client is the same one introduced in the [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts) — only the redemption-event polling needs it on this script's path. All the FXRP transfer activity happens through the Sepolia clients. The FXRP send amount is derived from the `SEND_LOTS` config via `calculateAmountToSend`. A single FXRP lot is a unit of redeemable FXRP determined by the FXRP `AssetManager` settings; the helper multiplies the lot count by `lotSizeAMG × assetMintingGranularityUBA` to get the FXRP base-unit amount. ```typescript title="src/utils/fassets.ts" export async function calculateAmountToSend(lots: bigint): Promise { const assetManagerAddress = await getAssetManagerFXRPAddress(); const settings = await publicClient.readContract({ address: assetManagerAddress, abi: coston2.iAssetManagerAbi, functionName: "getSettings", }); return ( lots * BigInt(settings.lotSizeAMG) * BigInt(settings.assetMintingGranularityUBA) ); } ``` The `getAssetManagerFXRPAddress` helper is covered in the [State Lookup guide](/smart-accounts/guides/typescript-viem/state-lookup-ts#accounts-fxrp-balance). ## The compose message A LayerZero compose message is opaque calldata that the destination chain's executor passes to a contract after the OFT credit has been applied. The redemption composer on Flare expects a tuple of `(uint256 amount, string underlyingAddress, address redeemer)`. ```typescript title="src/cross-chain-redeem.ts" function encodeComposeMessage( amountToSend: bigint, underlyingAddress: string, redeemer: Address, ): `0x${string}` { return encodeAbiParameters( [{ type: "uint256" }, { type: "string" }, { type: "address" }], [amountToSend, underlyingAddress, redeemer], ); } ``` The `amountToSend` parameter is the FXRP amount in base units, the `underlyingAddress` is the XRPL address to receive the XRP, and the `redeemer` is the Sepolia account credited as the redeemer on the `AssetManager`. ## Compose and executor options LayerZero needs to know how much gas to budget on the destination chain for two things: the regular `lzReceive` that credits the OFT, and the additional `lzCompose` that executes the composer's redemption logic. ```typescript title="src/cross-chain-redeem.ts" function buildComposeOptions(): `0x${string}` { return Options.newOptions() .addExecutorLzReceiveOption(CONFIG.EXECUTOR_GAS, 0) .addExecutorComposeOption(0, CONFIG.COMPOSE_GAS, 0) .toHex() as `0x${string}`; } ``` Both `EXECUTOR_GAS` and `COMPOSE_GAS` are set to `1_000_000`, which is generous enough to cover the OFT credit plus a redemption request and event emission on Flare. The first argument of `addExecutorComposeOption` is the compose index (`0`, since there is exactly one composed call); the last argument of each is the value attached to the call (`0` in both cases). ## Building the `SendParam` The OFT `send` function takes a `SendParam` struct. The recipient is the composer contract — padded to 32 bytes — so that LayerZero delivers the credit to the composer rather than to a user wallet. ```typescript title="src/cross-chain-redeem.ts" function buildSendParam( amountToSend: bigint, composeMsg: `0x${string}`, extraOptions: `0x${string}`, ): SendParam { return { dstEid: CONFIG.COSTON2_EID, to: pad(CONFIG.COSTON2_COMPOSER, { size: 32 }), amountLD: amountToSend, minAmountLD: amountToSend, extraOptions, composeMsg, oftCmd: "0x", }; } ``` The `amountLD` and `minAmountLD` parameters are set to the same value because no slippage is expected on a 1:1 OFT bridge. The `oftCmd` argument is empty for the standard `SEND` operation. ## Balance check and fee quote Before broadcasting, the script confirms that the signer has enough FXRP and quotes the native fee that LayerZero will charge on Sepolia. ```typescript title="src/cross-chain-redeem.ts" const balance = await sepoliaPublicClient.readContract({ address: oftAddress, abi: fxrpOftAbi, functionName: "balanceOf", args: [signerAddress], }); ``` ```typescript title="src/cross-chain-redeem.ts" const { nativeFee, lzTokenFee } = await sepoliaPublicClient.readContract({ address: oftAddress, abi: fxrpOftAbi, functionName: "quoteSend", args: [sendParam, false], }); ``` The fee is denominated in ETH because that is the chain the transaction originates on. The `quoteSend` function accounts for the compose payload — a larger `composeMsg` increases the quoted fee. ## Sending the transaction The send is performed with the standard Viem `simulateContract` + `writeContract` pattern, attaching the quoted native fee as the transaction value. ```typescript title="src/cross-chain-redeem.ts" const { request } = await sepoliaPublicClient.simulateContract({ account, address: oftAddress, abi: fxrpOftAbi, functionName: "send", args: [sendParam, { nativeFee, lzTokenFee }, signerAddress], value: nativeFee, }); const txHash = await sepoliaWalletClient.writeContract(request); ``` The third argument of `send` is the refund address — any unused portion of `nativeFee` is returned there. Once the Sepolia receipt is in, the LayerZero scanner link is printed so the user can follow the cross-chain delivery in real time. ## Waiting for `RedemptionRequested` Event A successful flow results in the `AssetManager` on Flare emitting a [`RedemptionRequested`](/fassets/reference/IAssetManagerEvents#redemptionrequested) event with the composer as the `redeemer` indexed argument. The `waitForRedemptionOnCoston2` helper polls Flare in 25-block chunks (the Coston2 RPC caps `eth_getLogs` at 30 blocks per query) for up to 5 minutes. ```typescript title="src/cross-chain-redeem.ts" const REDEMPTION_TIMEOUT_MS = 5 * 60 * 1000; const REDEMPTION_POLL_INTERVAL_MS = 10_000; const MAX_BLOCK_RANGE = 25n; async function waitForRedemptionOnCoston2(fromBlock: bigint) { const assetManagerAddress = await getAssetManagerFXRPAddress(); const deadline = Date.now() + REDEMPTION_TIMEOUT_MS; let cursor = fromBlock; while (Date.now() < deadline) { const latest = await publicClient.getBlockNumber(); while (cursor <= latest) { const chunkEnd = cursor + MAX_BLOCK_RANGE - 1n; const toBlock = chunkEnd > latest ? latest : chunkEnd; const logs = await publicClient.getContractEvents({ address: assetManagerAddress, abi: coston2.iAssetManagerAbi, eventName: "RedemptionRequested", args: { redeemer: CONFIG.COSTON2_COMPOSER }, fromBlock: cursor, toBlock, }); if (logs.length > 0) { return logs[0]!; } cursor = toBlock + 1n; } await new Promise((resolve) => setTimeout(resolve, REDEMPTION_POLL_INTERVAL_MS), ); } throw new Error( `RedemptionRequested event not observed within ${REDEMPTION_TIMEOUT_MS}ms`, ); } ``` The inner loop advances `cursor` chunk-by-chunk through any new blocks that have appeared, then sleeps 10 seconds before re-checking the chain tip. Filtering by `redeemer` on the indexed argument means the call only returns events tied to the composer this script is targeting. The actual XRP payment to `underlyingAddress` is handled off-chain by an FAsset agent after the redemption request is filed. That step is asynchronous and is not awaited by this script. ## Full script The repository with the above example is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter). In the example repository, certain helpers are isolated into separate files in the `src/utils` directory, and the shared `SendParam` type lives in `src/layer-zero/types.ts`. {CrossChainRedeemScript} ## Expected output ```bash > pnpm run script src/layer-zero/cross-chain-redeem.ts > @flarenetwork/flare-smart-accounts-viem@0.0.1 script ~/flare-smart-accounts-viem > tsx --env-file=.env src/layer-zero/cross-chain-redeem.ts Using account: 0xF5488132432118596fa13800B68df4C0fF25131d Composer configured: 0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 Connecting to FXRP OFT on Sepolia: 0x81672c5d42F3573aD95A0bdfBE824FaaC547d4E6 Token decimals: 6 Redemption Parameters: Amount: 10000000 FXRP base units XRP Address: rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp Redeemer: 0xF5488132432118596fa13800B68df4C0fF25131d Current FXRP balance: 80 LayerZero Fee: 0.000101716112596574 ETH Sending 10 FXRP to Coston2 with auto-redeem... Target composer: 0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 Transaction sent: 0x92f51cdc9b7ed4c9c63876340c843415ee5d5721624fadeb2549bfc539a657e7 Confirmed in block: 10838150n Track your cross-chain transaction: https://testnet.layerzeroscan.com/tx/0x92f51cdc9b7ed4c9c63876340c843415ee5d5721624fadeb2549bfc539a657e7 The auto-redeem will execute once the message arrives on Coston2. XRP will be sent to: rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp Waiting for RedemptionRequested event on Coston2... RedemptionRequested event observed on Coston2: { eventName: 'RedemptionRequested', args: { agentVault: '0xd5dEFe2c62D48788BB3889534FBFe7Aea0602D64', redeemer: '0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0', requestId: 29197906n, paymentAddress: 'rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp', valueUBA: 10000000n, feeUBA: 50000n, firstUnderlyingBlock: 17306757n, lastUnderlyingBlock: 17307376n, lastUnderlyingTimestamp: 1778579481n, paymentReference: '0x4642505266410002000000000000000000000000000000000000000001bd8652', executor: '0x0000000000000000000000000000000000000000', executorFeeNatWei: 0n }, address: '0xc1ca88b937d0b528842f95d5731ffb586f4fbdfa', topics: [ '0x8cbbd73a8d1b8b02a53c4c3b0ee34b472fe3099cc19bcfb57f1aae09e8a9847e', '0x000000000000000000000000d5defe2c62d48788bb3889534fbfe7aea0602d64', '0x0000000000000000000000005051e8db650e9e0e2a3f03010ee5c60e79cf583e', '0x0000000000000000000000000000000000000000000000000000000001bd8652' ], data: '0x00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000c350000000000000000000000000000000000000000000000000000000000108148500000000000000000000000000000000000000000000000000000000010816f0000000000000000000000000000000000000000000000000000000006a02f8194642505266410002000000000000000000000000000000000000000001bd8652000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022727048757734624b536a6f6e4b52724b4b5659555a595956656467316a7950726d70000000000000000000000000000000000000000000000000000000000000', blockNumber: 30438649n, transactionHash: '0x9d82fe98f04fa37f729c786cab99632c781b9f3bc655cdd040a8adcfc497f736', transactionIndex: 1, blockHash: '0xa006cf7988226e5e57a1194e9c5a183beec2a37d999f966199f943fe044a77a2', logIndex: 9, removed: false, blockTimestamp: undefined } ``` ## What's next - [Cross-Chain Mint guide](/smart-accounts/guides/typescript-viem/cross-chain-mint-ts) - [Cross-Chain Redeem to Tag guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-to-tag-ts) - [Direct Minting overview](/fassets/direct-minting) --- ## Cross-Chain Redeem to Tag This guide is a variant of the [Cross-Chain Redeem guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-ts) that redeems FXRP to an XRPL address with a [destination tag](https://xrpl.org/docs/concepts/transactions/source-and-destination-tags). Read the redeem guide first — only the differences are covered here. :::info The code in this guide is set up for the Coston2 testnet and the Sepolia testnet. Despite that, we will refer to the Flare-side network as Flare and its currency as FLR instead of Coston2 and C2FLR. Likewise, we will refer to the Sepolia testnet's currency as ETH instead of SETH. ::: ## What's different Three things change relative to the tagless redeem: - the compose message is a 6-field struct instead of a 3-tuple — it carries the destination tag and reserves space for an optional executor and executor fee. - a new environment variable `REDEMPTION_DESTINATION_TAG` controls the XRPL destination tag (defaulting to `72`). - the script waits for a `FAssetRedeemed` event on the composer instead of a `RedemptionRequested` event on the `AssetManager`. Both scripts target the same composer contract (default `0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0`). The rest of the flow — quoting the LayerZero fee, building the `SendParam`, checking the balance, sending the transaction, paying for the cross-chain message in ETH — is identical to the [Cross-Chain Redeem guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-ts). ## The compose message The destination composer expects a single ABI-encoded struct that mirrors the on-chain `IFAssetRedeemComposer.RedeemComposeMessage` type. ```typescript title="src/cross-chain-redeem-to-tag.ts" const redeemComposeMessageAbi = [ { type: "tuple", components: [ { name: "redeemer", type: "address" }, { name: "redeemerUnderlyingAddress", type: "string" }, { name: "redeemWithTag", type: "bool" }, { name: "destinationTag", type: "uint256" }, { name: "executor", type: "address" }, { name: "executorFee", type: "uint256" }, ], }, ] as const; ``` The two `executor`-related fields support paying a third party to advance the redemption on the user's behalf. In this script they are set to the zero address and zero amount, so the user themselves drives the flow. ```typescript title="src/cross-chain-redeem-to-tag.ts" function encodeComposeMessage( redeemer: Address, underlyingAddress: string, destinationTag: bigint, ): `0x${string}` { return encodeAbiParameters(redeemComposeMessageAbi, [ { redeemer, redeemerUnderlyingAddress: underlyingAddress, redeemWithTag: true, destinationTag, executor: zeroAddress, executorFee: 0n, }, ]); } ``` The `redeemWithTag` argument is hardcoded to `true` — that is the whole point of this guide. For tagless redemption against the same composer, see the [Cross-Chain Redeem guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-ts). :::warning Before the script can use `REDEMPTION_DESTINATION_TAG`, the tag has to be reserved on the [`IMintingTagManager`](/fassets/reference/IMintingTagManager) (via [`reserve()`](/fassets/reference/IMintingTagManager#reserve)) and bound to the redeemer with [`setMintingRecipient(tag, recipient)`](/fassets/reference/IMintingTagManager#setmintingrecipient). Without that registration the composer will refuse to process the redemption. See the [Redeem with Tag](/fassets/redemption#redeem-with-tag) section of the Redemption guide for the registration flow. ::: ## Waiting for `FAssetRedeemed` Because the composer is the contract that pulls the trigger on the redemption, it emits its own event that fires after the underlying `AssetManager.redeem` call succeeds. The polling loop is the same shape as `waitForRedemptionOnCoston2` in the tagless redeem (25-block chunks, 5-minute timeout) but reads from the composer's ABI and filters on the user as the `redeemer`. ```typescript title="src/cross-chain-redeem-to-tag.ts" async function waitForFAssetRedeemedOnCoston2( redeemer: Address, fromBlock: bigint, ) { const deadline = Date.now() + REDEMPTION_TIMEOUT_MS; let cursor = fromBlock; while (Date.now() < deadline) { const latest = await publicClient.getBlockNumber(); while (cursor <= latest) { const chunkEnd = cursor + MAX_BLOCK_RANGE - 1n; const toBlock = chunkEnd > latest ? latest : chunkEnd; const logs = await publicClient.getContractEvents({ address: CONFIG.COSTON2_COMPOSER, abi: coston2.ifAssetRedeemComposerAbi, eventName: "FAssetRedeemed", args: { redeemer }, fromBlock: cursor, toBlock, }); if (logs.length > 0) { return logs[0]!; } cursor = toBlock + 1n; } await new Promise((resolve) => setTimeout(resolve, REDEMPTION_POLL_INTERVAL_MS), ); } throw new Error( `FAssetRedeemed event not observed within ${REDEMPTION_TIMEOUT_MS}ms`, ); } ``` The `redeemer` argument here is the Sepolia signer address — i.e. the user — which makes the event easy to filter even if multiple unrelated redemptions are in flight against the same composer. All the other helpers used by this script (Sepolia client setup, `calculateAmountToSend`, the `SendParam` builder, the balance check, fee quote, and send path) are unchanged from the [Cross-Chain Redeem guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-ts). ## Full script The repository with the above example is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter). {CrossChainRedeemToTagScript} ## Expected output ```bash Using account: 0xF5488132432118596fa13800B68df4C0fF25131d Composer configured: 0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 Connecting to FXRP OFT on Sepolia: 0x81672c5d42F3573aD95A0bdfBE824FaaC547d4E6 Token decimals: 6 Redemption Parameters: Amount: 10000000 FXRP base units XRP Address: rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp Redeemer: 0xF5488132432118596fa13800B68df4C0fF25131d Destination tag: 72 Current FXRP balance: 70 LayerZero Fee: 0.000101716112596574 ETH Sending 10 FXRP to Coston2 with auto-redeem-with-tag... Target composer: 0xa10569DFb38FE7Be211aCe4E4A566Cea387023b0 Transaction sent: 0xca0e2558b4397beef13c8b7b0acf6e9ecf42b22ea93aeec7e837656d7f23674c Confirmed in block: 10838169n Track your cross-chain transaction: https://testnet.layerzeroscan.com/tx/0xca0e2558b4397beef13c8b7b0acf6e9ecf42b22ea93aeec7e837656d7f23674c The auto-redeem-with-tag will execute once the message arrives on Coston2. XRP will be sent to: rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp Destination tag: 72 Waiting for FAssetRedeemed event on Coston2 composer... FAssetRedeemed event observed on Coston2: { eventName: 'FAssetRedeemed', args: { guid: '0xaebbeadcb6b78b8ab0c541cefec4a5b9d037d4f069a7f333fb8b16f9b6439cb3', srcEid: 40161, redeemer: '0xF5488132432118596fa13800B68df4C0fF25131d', redeemerAccount: '0xEB947e97BD6b02C4121D2AdBAa1e07A4cA3D8D1E', amountToRedeemUBA: 9990000n, redeemerUnderlyingAddress: 'rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp', redeemWithTag: true, destinationTag: 72n, executor: '0x103b384064ae85577127097A7cCadfd6fb13f437', executorFee: 0n, redeemedAmountUBA: 9990000n, wrappedAmount: 0n }, address: '0xa10569dfb38fe7be211ace4e4a566cea387023b0', topics: [ '0x9e8ed1306ec6ca165a2c23ae451b161741d3da7ab41fe134775ebd94b2351f04', '0xaebbeadcb6b78b8ab0c541cefec4a5b9d037d4f069a7f333fb8b16f9b6439cb3', '0x0000000000000000000000000000000000000000000000000000000000009ce1', '0x000000000000000000000000f5488132432118596fa13800b68df4c0ff25131d' ], data: '0x000000000000000000000000eb947e97bd6b02c4121d2adbaa1e07a4ca3d8d1e0000000000000000000000000000000000000000000000000000000000986f70000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000048000000000000000000000000103b384064ae85577127097a7ccadfd6fb13f43700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000986f7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022727048757734624b536a6f6e4b52724b4b5659555a595956656467316a7950726d70000000000000000000000000000000000000000000000000000000000000', blockNumber: 30438770n, transactionHash: '0xc1d39bc7383e856a92f9a3a60f1b33f89926a21c68910f29a3566c1a0f557679', transactionIndex: 9, blockHash: '0x2071fdbb5d07266f380a40c819818fff507f7142340961681af5a8ec796e6b09', logIndex: 18, removed: false, blockTimestamp: undefined } ``` ## What's next - [Cross-Chain Mint guide](/smart-accounts/guides/typescript-viem/cross-chain-mint-ts) - [Cross-Chain Redeem guide](/smart-accounts/guides/typescript-viem/cross-chain-redeem-ts) - [Direct Minting overview](/fassets/direct-minting) --- ## IMasterAccountController Interface for the `MasterAccountController` contract, which manages personal accounts and executes XRPL instructions on Flare. It uses the [Diamond pattern](https://eips.ethereum.org/EIPS/eip-2535) to compose all functionality into a single deployed contract. Sourced from `IMasterAccountController.sol` and its facets on [GitHub](https://github.com/flare-foundation/flare-smart-accounts/tree/main/contracts/userInterfaces). ## Personal Accounts ### `getPersonalAccount` Returns the [`PersonalAccount`](/smart-accounts/reference/IPersonalAccount) contract address for a given XRPL owner. A `PersonalAccount` is deployed only once the first instruction has been sent from its owner XRPL address. If the account has not yet been deployed, it returns the precomputed deterministic address. ```solidity function getPersonalAccount( string calldata _xrplOwner ) external view returns (address); ``` Parameters: - `_xrplOwner`: The XRPL address of the owner. Returns: - The [`PersonalAccount`](/smart-accounts/reference/IPersonalAccount) contract address, or the computed address if not yet deployed. ## Instructions ### `executeInstruction` Executes an XRPL instruction for a given XRPL address using a [Flare Data Connector](/fdc/overview) payment proof. ```solidity function executeInstruction( IPayment.Proof calldata _proof, string calldata _xrplAddress ) external payable; ``` Parameters: - `_proof`: Proof of the XRPL payment transaction that comes from the [Flare Data Connector](/fdc/overview). - `_xrplAddress`: The XRPL address that sent the payment. ### `reserveCollateral` Reserves collateral for a minting operation. Called by the executor service after receiving a collateral reservation instruction from an XRPL payment. ```solidity function reserveCollateral( string calldata _xrplAddress, bytes32 _paymentReference, bytes32 _transactionId ) external payable returns (uint256 _collateralReservationId); ``` Parameters: - `_xrplAddress`: The XRPL address requesting the collateral reservation. - `_paymentReference`: The payment reference from the XRPL transaction memo. - `_transactionId`: The unique XRPL transaction ID for tracking. Returns: - `_collateralReservationId`: The ID of the collateral reservation. ### `executeDepositAfterMinting` Executes a vault deposit after minting is complete for a given collateral reservation. ```solidity function executeDepositAfterMinting( uint256 _collateralReservationId, IPayment.Proof calldata _proof, string calldata _xrplAddress ) external; ``` Parameters: - `_collateralReservationId`: The ID of the collateral reservation returned by `reserveCollateral`. - `_proof`: Proof of the XRPL payment transaction. - `_xrplAddress`: The XRPL address requesting execution. ### `isTransactionIdUsed` Returns `true` if the given XRPL transaction ID has already been executed. ```solidity function isTransactionIdUsed( bytes32 _transactionId ) external view returns (bool); ``` ### `getTransactionIdForCollateralReservation` Returns the XRPL transaction ID associated with a collateral reservation. ```solidity function getTransactionIdForCollateralReservation( uint256 _collateralReservationId ) external view returns (bytes32 _transactionId); ``` ## Instruction Fees ### `getDefaultInstructionFee` Returns the default fee applied to instructions without a specific per-instruction fee configured. Denominated in the underlying asset's smallest unit (i.e., XRP drops). ```solidity function getDefaultInstructionFee() external view returns (uint256); ``` ### `getInstructionFee` Returns the fee for a specific instruction ID (first byte on an encoded instruction). ```solidity function getInstructionFee( uint256 _instructionId ) external view returns (uint256); ``` Parameters: - `_instructionId`: The ID of the instruction. ## Agent Vaults ### `getAgentVaults` Returns the list of registered FAssets agent vault IDs and their corresponding addresses. ```solidity function getAgentVaults() external view returns (uint256[] memory _agentVaultIds, address[] memory _agentVaultAddresses); ``` Returns: - `_agentVaultIds`: The list of registered agent vault IDs. - `_agentVaultAddresses`: The list of registered agent vault addresses. ## Vaults ### `getVaults` Returns the list of registered vaults as arrays of their IDs, addresses, and types (Firelight or Upshift). ```solidity function getVaults() external view returns ( uint256[] memory _vaultIds, address[] memory _vaultAddresses, uint8[] memory _vaultTypes ); ``` Returns: - `_vaultIds`: The list of registered vault IDs. - `_vaultAddresses`: The list of vault addresses. - `_vaultTypes`: The list of vault types (`1` = Firelight, `2` = Upshift). ## Executor ### `getExecutorInfo` Returns the executor address and fee. The executor is the service that calls [`reserveCollateral`](/smart-accounts/reference/IMasterAccountController#reservecollateral) and [`executeDepositAfterMinting`](/smart-accounts/reference/IMasterAccountController#executedepositafterminting) on behalf of users. ```solidity function getExecutorInfo() external view returns (address payable _executor, uint256 _executorFee); ``` Returns: - `_executor`: The executor address. - `_executorFee`: The executor fee in wei. ## XRPL Provider Wallets ### `getXrplProviderWallets` Returns a list of XRPL operator wallet addresses to which users send payment transactions. ```solidity function getXrplProviderWallets() external view returns (string[] memory); ``` ## Payment Proofs ### `getSourceId` Returns the source ID used to identify the XRPL chain for payment proof verification. ```solidity function getSourceId() external view returns (bytes32); ``` ### `getPaymentProofValidityDurationSeconds` Returns the maximum age (in seconds) of an XRPL payment proof for it to be accepted. ```solidity function getPaymentProofValidityDurationSeconds() external view returns (uint256); ``` ## Memo Instructions Memo instructions allow a [`PersonalAccount`](/smart-accounts/reference/IPersonalAccount) to execute [custom user operations](/smart-accounts/custom-instruction) embedded in the XRPL `Payment` memo when FAssets are direct-minted into the account. ### `mintedFAssets` Called by the FAssets `AssetManager` when FXRP is direct-minted into a personal account. Decodes the XRPL memo, distributes the minted FAssets between the personal account and the executor, and dispatches the custom instruction. See [Custom Instruction](/smart-accounts/custom-instruction) for the end-to-end flow. ```solidity function mintedFAssets( bytes32 _transactionId, string calldata _sourceAddress, uint256 _amount, uint256 _underlyingTimestamp, bytes calldata _memoData, address payable _executor ) external payable; ``` Parameters: - `_transactionId`: The XRPL transaction ID for replay protection. - `_sourceAddress`: The XRPL source address that triggered the mint. - `_amount`: The total minted FAsset amount, in the FAsset's smallest unit (XRP drops for FXRP). - `_underlyingTimestamp`: The XRPL transaction timestamp. - `_memoData`: The raw XRPL memo bytes carrying the [memo instruction](/smart-accounts/custom-instruction). - `_executor`: The executor address; must equal the personal account's pinned executor when one is set. The `AssetManager` is the only address allowed to call this function — any other caller reverts with [`OnlyAssetManager`](#onlyassetmanager). ### `getNonce` Returns the current memo-instruction nonce for a personal account. A `PackedUserOperation`'s `nonce` field must equal this value to be accepted; the nonce auto-increments on every successful execution. :::warning[Nonce collisions] Only one user operation can consume a given nonce. If several XRPL flows or off-chain builders read the same `getNonce` value and each embeds a **different** `PackedUserOperation` with that nonce, whichever mint executes first succeeds and increments the nonce; the others revert with [`InvalidNonce`](#invalidnonce). ::: ```solidity function getNonce( address _personalAccount ) external view returns (uint256); ``` Parameters: - `_personalAccount`: The personal account address to query. ### `getExecutor` Returns the executor pinned to a personal account, or `address(0)` if no executor is pinned. When set, only that executor can deliver memos to the account (except for the executor-management memos `0xD0` and `0xD1`). ```solidity function getExecutor( address _personalAccount ) external view returns (address); ``` Parameters: - `_personalAccount`: The personal account address to query. ## Events ### Instructions #### `CollateralReserved` Emitted when collateral is reserved for minting. ```solidity event CollateralReserved( address indexed personalAccount, bytes32 indexed transactionId, bytes32 indexed paymentReference, string xrplOwner, uint256 collateralReservationId, address agentVault, uint256 lots, address executor, uint256 executorFee ); ``` #### `InstructionExecuted` Emitted when an instruction is executed. ```solidity event InstructionExecuted( address indexed personalAccount, bytes32 indexed transactionId, bytes32 indexed paymentReference, string xrplOwner, uint256 instructionId ); ``` #### `FXrpRedeemed` Emitted when an FXRP redemption is performed. ```solidity event FXrpRedeemed( address indexed personalAccount, uint256 lots, uint256 amount, address executor, uint256 executorFee ); ``` #### `FXrpTransferred` Emitted when FXRP is transferred from a personal account. ```solidity event FXrpTransferred( address indexed personalAccount, address to, uint256 amount ); ``` #### `Approved` Emitted when a token approval is made for a vault. ```solidity event Approved( address indexed personalAccount, address fxrp, address vault, uint256 amount ); ``` #### `Deposited` Emitted when a deposit is made to a vault. ```solidity event Deposited( address indexed personalAccount, address indexed vault, uint256 amount, uint256 shares ); ``` #### `Redeemed` Emitted when a redemption is made from a vault. ```solidity event Redeemed( address indexed personalAccount, address indexed vault, uint256 shares, uint256 amount ); ``` #### `WithdrawalClaimed` Emitted when a withdrawal claim is completed. ```solidity event WithdrawalClaimed( address indexed personalAccount, address indexed vault, uint256 period, uint256 amount ); ``` #### `RedeemRequested` Emitted when a redeem request is made from a vault. ```solidity event RedeemRequested( address indexed personalAccount, address indexed vault, uint256 shares, uint256 claimableEpoch, uint256 year, uint256 month, uint256 day ); ``` #### `Claimed` Emitted when a claim is made for a specific date. ```solidity event Claimed( address indexed personalAccount, address indexed vault, uint256 year, uint256 month, uint256 day, uint256 shares, uint256 amount ); ``` ### Memo Instructions #### `UserOperationExecuted` Emitted when a memo-encoded user operation (`0xFF`) executes with the personal account. ```solidity event UserOperationExecuted( address indexed personalAccount, uint256 nonce ); ``` Parameters: - `personalAccount`: The personal account that ran the user operation. - `nonce`: The nonce consumed by the user operation. #### `IgnoreMemoSet` Emitted when an `0xE0` memo marks a target XRPL transaction's memo to be skipped on its next direct mint. ```solidity event IgnoreMemoSet( address indexed personalAccount, bytes32 indexed targetTxId ); ``` Parameters: - `personalAccount`: The personal account that owns the ignore flag. - `targetTxId`: The XRPL transaction ID whose memo will be skipped. #### `NonceIncreased` Emitted when an `0xE1` memo fast-forwards the personal account's memo-instruction nonce. ```solidity event NonceIncreased( address indexed personalAccount, uint256 newNonce ); ``` Parameters: - `personalAccount`: The personal account whose nonce was advanced. - `newNonce`: The new nonce value. #### `ExecutorSet` Emitted when an `0xD0` memo pins an executor to the personal account. ```solidity event ExecutorSet( address indexed personalAccount, address indexed executor ); ``` Parameters: - `personalAccount`: The personal account. - `executor`: The pinned executor address. #### `ExecutorRemoved` Emitted when an `0xD1` memo unpins the executor from the personal account. ```solidity event ExecutorRemoved( address indexed personalAccount ); ``` Parameters: - `personalAccount`: The personal account whose executor was cleared. #### `ReplacementFeeSet` Emitted when an `0xE2` memo sets a replacement executor fee for a stuck XRPL transaction. ```solidity event ReplacementFeeSet( address indexed personalAccount, bytes32 indexed targetTxId, uint64 newFee ); ``` Parameters: - `personalAccount`: The personal account. - `targetTxId`: The XRPL transaction ID whose fee is being replaced. - `newFee`: The new executor fee, in the FAsset's smallest unit. #### `DirectMintingExecuted` Emitted when direct minting credits FAssets to a personal account. ```solidity event DirectMintingExecuted( address indexed personalAccount, bytes32 indexed transactionId, string sourceAddress, uint256 amount, uint256 executorFee, address executor ); ``` Parameters: - `personalAccount`: The recipient personal account. - `transactionId`: The XRPL transaction ID. - `sourceAddress`: The XRPL source address that triggered the mint. - `amount`: The total minted amount (before deducting the executor fee). - `executorFee`: The fee paid to the executor. - `executor`: The executor address that delivered the mint. ## Errors ### Memo Instructions These errors are surfaced by [`mintedFAssets`](#mintedfassets) and the memo dispatch path. #### `TransactionAlreadyExecuted` Thrown when an XRPL transaction ID has already been processed. ```solidity error TransactionAlreadyExecuted(); ``` #### `OnlyAssetManager` Thrown when [`mintedFAssets`](#mintedfassets) is called by any address other than the FAssets `AssetManager`. ```solidity error OnlyAssetManager(); ``` #### `InsufficientAmountForFee` Thrown when the minted amount is smaller than the executor fee. ```solidity error InsufficientAmountForFee( uint256 amount, uint256 fee ); ``` Parameters: - `amount`: The minted amount. - `fee`: The executor fee. #### `WrongExecutor` Thrown when the delivered executor does not match the executor pinned to the personal account. ```solidity error WrongExecutor( address expected, address actual ); ``` Parameters: - `expected`: The executor pinned via [`getExecutor`](#getexecutor). - `actual`: The executor that delivered the mint. #### `InvalidMemoData` Thrown when the memo payload does not match the expected length for the given memo instruction ID. ```solidity error InvalidMemoData(); ``` #### `InvalidInstructionId` Thrown when the first byte of the memo is not a recognized memo instruction ID (`0xFF`, `0xE0`, `0xE1`, `0xE2`, `0xD0`, `0xD1`). ```solidity error InvalidInstructionId( uint8 instructionId ); ``` Parameters: - `instructionId`: The unrecognized memo instruction ID. #### `InvalidSender` Thrown when the `sender` field of the embedded `PackedUserOperation` does not match the personal account derived from the XRPL source address. ```solidity error InvalidSender( address sender, address personalAccount ); ``` Parameters: - `sender`: The sender field of the user operation. - `personalAccount`: The expected personal account address. #### `InvalidNonce` Thrown when the `nonce` field of the embedded `PackedUserOperation` does not equal the personal account's current nonce. ```solidity error InvalidNonce( uint256 expected, uint256 actual ); ``` Parameters: - `expected`: The nonce currently stored for the personal account. - `actual`: The nonce supplied in the user operation. #### `InvalidNonceIncrease` Thrown by an `0xE1` memo when the requested nonce is not strictly greater than the current nonce, or jumps by more than `type(uint32).max`. ```solidity error InvalidNonceIncrease( uint256 currentNonce, uint256 newNonce ); ``` Parameters: - `currentNonce`: The current nonce value. - `newNonce`: The requested new nonce value. #### `CallFailed` Thrown when the `callData` carried by the user operation reverts when invoked on the personal account. ```solidity error CallFailed( bytes returnData ); ``` Parameters: - `returnData`: The raw return data from the failed call. #### `AddressZero` Thrown when an address parsed from a memo payload (for example, the executor in an `0xD0` memo) is the zero address. ```solidity error AddressZero(); ``` #### `ValueZero` Thrown when a value parsed from a payment reference is zero. ```solidity error ValueZero(); ``` --- ## IPersonalAccount Interface for the `PersonalAccount` contract — Flare account abstraction for an XRPL address, controller entirely by that address. Sourced from `IPersonalAccount.sol` on [GitHub](https://github.com/flare-foundation/flare-smart-accounts/blob/main/contracts/userInterfaces/IPersonalAccount.sol). --- ## View Functions ### `xrplOwner` Returns the XRPL owner address associated with this personal account. ```solidity function xrplOwner() external view returns (string memory); ``` :::tip Smart account detection If a contract at a given Flare address returns an XRPL account when the `xrplOwner()` function is called, then it is a smart account. Use this to [detect whether an arbitrary Flare address is a smart account](/smart-accounts/guides/typescript-viem/state-lookup-ts#checking-if-a-flare-address-is-a-smart-account). ::: ### `controllerAddress` Returns the `MasterAccountController` address that manages this personal account. ```solidity function controllerAddress() external view returns (address); ``` ### `implementation` Returns the implementation contract address for this personal account (used by the beacon proxy pattern). ```solidity function implementation() external view returns (address); ``` ## State-Changing Functions ### `executeUserOp` Executes a series of arbitrary calls from this personal account as a single user operation. Each call is made with the personal account as `msg.sender`, allowing the account to interact with any contract on Flare. The function is `payable` so the account can forward FLR alongside the calls. ```solidity function executeUserOp( Call[] calldata _calls ) external payable; ``` Parameters: - `_calls`: Array of [`Call`](#call) structs describing each call to execute. Reverts with [`CallFailed(index, returnData)`](#callfailed) if any call in the array fails, where `index` identifies the failing call and `returnData` is the raw revert data returned by the target. ## Structs ### `Call` Describes a single call dispatched by [`executeUserOp`](#executeuserop). ```solidity struct Call { address target; uint256 value; bytes data; } ``` Fields: - `target`: The target contract address to call. - `value`: The amount of FLR (in wei) to send with the call. - `data`: The ABI-encoded calldata to pass to the target. ## Errors ### `CallFailed` Thrown by [`executeUserOp`](#executeuserop) when one of the underlying calls reverts. ```solidity error CallFailed(uint256 index, bytes returnData); ``` Parameters: - `index`: The index of the failing call within the `_calls` array. - `returnData`: The raw return data from the failed call. ### `InsufficientFundsForCollateralReservation` Thrown when the value sent with a collateral reservation is not enough to cover both the collateral reservation fee and the executor fee. ```solidity error InsufficientFundsForCollateralReservation( uint256 collateralReservationFee, uint256 executorFee ); ``` Parameters: - `collateralReservationFee`: The required collateral reservation fee. - `executorFee`: The required executor fee. ### `InsufficientFundsForRedeem` Thrown when the value sent with a redeem operation is not enough to cover the executor fee. ```solidity error InsufficientFundsForRedeem(uint256 executorFee); ``` Parameters: - `executorFee`: The required executor fee. ### `OnlyController` Thrown when a function restricted to the [`MasterAccountController`](/smart-accounts/reference/IMasterAccountController) is called by any other address. ```solidity error OnlyController(); ``` ### `AlreadyInitialized` Thrown when the personal account initializer is called more than once. ```solidity error AlreadyInitialized(); ``` ### `InvalidControllerAddress` Thrown when the controller address provided at initialization is the zero address or otherwise invalid. ```solidity error InvalidControllerAddress(); ``` ### `InvalidXrplOwner` Thrown when the XRPL owner string provided at initialization is empty or otherwise invalid. ```solidity error InvalidXrplOwner(); ``` ### `AgentNotAvailable` Thrown when the FAssets agent selected for a minting or redemption operation is not available. ```solidity error AgentNotAvailable(); ``` ### `ApprovalFailed` Thrown when an ERC-20 approval issued by the personal account (for example, when granting a vault permission to spend FXRP) returns `false` or reverts. ```solidity error ApprovalFailed(); ``` ## Events ### `CollateralReserved` Emitted when collateral is reserved for minting FXRP. ```solidity event CollateralReserved( address agentVault, uint256 lots, address executor, uint256 executorFee, uint256 collateralReservationId ); ``` Parameters: - `agentVault`: The agent vault address. - `lots`: The number of lots that will be minted. - `executor`: The executor address. - `executorFee`: The fee paid to the executor. - `collateralReservationId`: The ID of the collateral reservation. :::info This event does not include the necessary information to complete the minting process. When performing minting, the [`CollateralReserved`](/fassets/reference/IAssetManagerEvents#collateralreserved) event emitted by the `IAssetManager` contract should be observed instead. ::: ### `FXrpTransferred` Emitted when a transfer of FXRP is made from the personal account. ```solidity event FXrpTransferred( address to, uint256 amount ); ``` Parameters: - `to`: The recipient address. - `amount`: The amount of FXRP transferred. ### `FXrpRedeemed` Emitted when a FXRP redemption is performed. ```solidity event FXrpRedeemed( uint256 lots, uint256 amount, address executor, uint256 executorFee ); ``` Parameters: - `lots`: The number of lots redeemed. - `amount`: The amount redeemed. - `executor`: The executor address. - `executorFee`: The fee paid to the executor. ### `Approved` Emitted when a token approval is made for a vault. ```solidity event Approved( address fxrp, address vault, uint256 amount ); ``` Parameters: - `fxrp`: The FXRP token address. - `vault`: The vault address. - `amount`: The approved amount. ### `Deposited` Emitted when a deposit is made to a vault. ```solidity event Deposited( address indexed vault, uint256 amount, uint256 shares ); ``` Parameters: - `vault`: The vault address. - `amount`: The amount deposited. - `shares`: The number of shares received. ### `Redeemed` Emitted when a redeem is made from a vault. ```solidity event Redeemed( address indexed vault, uint256 amount, uint256 shares ); ``` Parameters: - `vault`: The vault address. - `amount`: The amount redeemed. - `shares`: The number of shares burned. ### `WithdrawalClaimed` Emitted when a withdrawal claim is made from a vault. ```solidity event WithdrawalClaimed( address indexed vault, uint256 period, uint256 amount ); ``` Parameters: - `vault`: The vault address. - `period`: The period for which the claim is made. - `amount`: The amount claimed. ### `RedeemRequested` Emitted when a redeem request is made from a vault. ```solidity event RedeemRequested( address indexed vault, uint256 shares, uint256 claimableEpoch, uint256 year, uint256 month, uint256 day ); ``` Parameters: - `vault`: The vault address. - `shares`: The number of shares to redeem. - `claimableEpoch`: The epoch when the claim becomes available. - `year`: The year of the claimable date. - `month`: The month of the claimable date. - `day`: The day of the claimable date. ### `Claimed` Emitted when a claim is made for a specific date. ```solidity event Claimed( address indexed vault, uint256 year, uint256 month, uint256 day, uint256 shares, uint256 amount ); ``` Parameters: - `vault`: The vault address. - `year`: The year of the claim. - `month`: The month of the claim. - `day`: The day of the claim. - `shares`: The number of shares claimed. - `amount`: The amount claimed. ### `SwapExecuted` Emitted when a token swap is executed. ```solidity event SwapExecuted( address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut ); ``` Parameters: - `tokenIn`: The input token address. - `tokenOut`: The output token address. - `amountIn`: The amount of input tokens. - `amountOut`: The amount of output tokens received. --- ## Flare Confidential Compute **F**lare **C**onfidential **C**ompute **(FCC)** extends the Flare blockchain with [Trusted Execution Environments (TEEs)](https://en.wikipedia.org/wiki/Trusted_execution_environment) to enable secure off-chain computation, cross-chain transaction signing, and fast data attestation. FCC provides infrastructure for building custom, secure TEE integrations through a system of **Flare Compute Extensions (FCE)**; useful for dealing with private data. It also delivers two built-in system applications: **Protocol Managed Wallets (PMW)** and a new generation of the [Flare Data Connector (FDC)](/fdc/overview). ## Key Features - **Secure Offchain Computation:** TEE machines run verifiable code in hardware-isolated environments, ensuring computation integrity even if the machine operator is untrusted. - **Cross-Chain Transaction Signing:** Protocol Managed Wallets enable programmable assembly and signing of transactions on external blockchains (XRPL, BTC) through smart contract calls on Flare. - **Fast Data Attestation:** A new TEE-based FDC enables rapid attestation of external data, where TEE machine signatures serve as proof of data provider consensus. - **Extensible Architecture:** Developers can build custom Flare Compute Extensions that run arbitrary computations within TEE machines, with results verifiable onchain. - **Decentralized Consensus:** Instructions are relayed to TEE machines only after reaching a 50%+ signature weight from Flare's data providers, leveraging the same [signing policy](/network/fsp) used across the Flare Systems Protocol. - **Private Key Management:** TEE machines securely generate, store, back up, and restore private keys, enabling multi-signature wallet operations across blockchains. :::danger Although the Flare confidential compute is in the final stages of development, it is not yet publicly available. Stay tuned for that and the upcoming guides. ::: ## Architecture ```mermaid graph TB subgraph FCCSystem["FCC System"] FC["FCC Contracts Extensions Registration & attestation Instruction emission Private key administration"] DP["Data Providers & Cosigners Instruction relaying Instruction augmentation FDC verification"] TEE["TEE Machines Identity Instruction verification Private key management Custom logic per extension"] end Users["Users Smart contracts Direct users"] --> FC EW["External World Blockchains Web2, exchanges, ..."] -- "Flare Data Connector" --> DP FC -- "instructions" --> DP DP -- "signed instructions" --> TEE TEE -- "registration & attestation" --> FC TEE -- "relaying results" --> FC DP -- "Flare Data Connector" --> TEE TEE -- "relaying results" --> EW style FCCSystem stroke-dasharray: 5 5 ``` The system comprises three core components: 1. **Smart Contracts** on the Flare blockchain govern the underlying logic. This includes the management of compute extensions, the registration and attestation of TEE machines, the issuance of instructions for secure relay to TEE machines, and the administration of private keys generated and stored within TEEs. 2. **Data Providers and Cosigners** function as instruction relayers. They parse instruction events from smart contracts, augment them with external data, sign them, and transmit them to TEE machines. Cosigners provide an additional layer of authorization for sensitive operations like payments and key management. 3. **TEE Machines** verify that instructions carry adequate consensus (threshold signature weight) from data providers and cosigners. Upon successful verification, the TEE machine executes the corresponding computation and signs the result with a relevant private key. Results include signed payment transactions for external blockchains, signed attestations, or other computation outputs usable within smart contracts. ## Flare Compute Extensions Applications within FCC are organized as **Flare Compute Extensions (FCE)**. Each compute extension represents an isolated set of functionalities running on TEE machines, extending the concept of smart contracts into TEE environments. A compute extension is defined by: - **Supported code versions**: Each code version is a hash of the Docker image running in the confidential VM and must be reproducible. - **Registered TEE machines**: Machines running supported code versions that have been registered with an onchain attestation proof. The FCC infrastructure provides the following for all compute extensions: - **Identity**: Each TEE machine has a unique identity (TEE id) defined by a private key generated at boot. - **Onchain Registration**: TEE machines register within a compute extension by proving they run a supported code version, verified through machine attestation and the FDC. - **Result Verification**: Data and computation results signed by a registered TEE identity can be trusted and verified onchain. - **Instruction Relaying**: Function calls on TEE machines are triggered through instruction events on Flare's smart contracts, securely relayed by data providers. - **Private Key Management**: Compute extensions support secure key generation, backup, and restoration across TEE machines. ### Deployment ```mermaid graph TB ExtUsers(["Users, data providers, cosigners"]) --> ExternalAPI subgraph Firewall Owner(["Owner"]) -- "/configure" --> TEEMachine subgraph TEEMachine["TEE Machine"] subgraph Docker["Docker Container"] ExtApp["Extension app"] <--> FlareApp["Flare TEE app"] end end TEEMachine -- "actions & results" --> InternalAPI["Internal API"] InternalAPI --> Proxy Proxy --> REDIS["REDIS"] Proxy --> Indexer["C-chain indexer"] Indexer --> FlareNode["Flare node"] end ExternalAPI["External API"] <==> Proxy style Firewall stroke-dasharray: 5 5 ``` Each TEE deployment consists of two main components: - **TEE Machine:** Runs inside a confidential virtual machine and is not publicly accessible. It sits behind a firewall and has a single configuration endpoint used by the owner. The TEE machine pulls actions from the proxy at its own pace, processes them, and pushes results back. - **TEE Proxy:** A publicly accessible server that acts as the interface between the outside world and the TEE machine. It receives signed instructions from data providers, manages action queues, stores results, and serves them to external users. The proxy also monitors the Flare C-chain for signing policy updates. ## Flare Confidential Compute Extensions Applications within FCC are organized as **Flare Compute Extensions (FCE)**. Each compute extension represents an isolated set of functionalities running on TEE machines, extending the concept of smart contracts into TEE environments. A compute extension is defined by: - **Supported code versions:** Each code version is a hash of the Docker image running in the confidential VM and must be reproducible. - **Registered TEE machines:** Machines running supported code versions that have been registered with an onchain attestation proof. The FCC infrastructure provides the following for all compute extensions: - **Identity:** Each TEE machine has a unique identity (TEE id) defined by a private key generated at boot. - **Onchain Registration:** TEE machines register within a compute extension by proving they run a supported code version, verified through machine attestation and the FDC. - **Result Verification:** Data and computation results signed by a registered TEE identity can be trusted and verified onchain. - **Instruction Relaying:** Function calls on TEE machines are triggered through instruction events on Flare's smart contracts, securely relayed by data providers. - **Private Key Management:** Compute extensions support secure key generation, backup, and restoration across TEE machines. ## System Applications ### Protocol Managed Wallets (PMW) PMW enables programmable assembly, signing, and execution of transactions on external blockchains through smart contract calls on Flare. This introduces blockchain abstraction and external execution capabilities on Flare. Key capabilities: - **Multisig Operations:** Wallets represent sets of private keys across multiple TEE machines, acting as signers on k-of-n native multisig accounts on external blockchains (XRPL, BTC), where any k of the n keys are sufficient to authorize a transaction. - **Nonce Management:** Each payment instruction is issued with a unique nonce. On UTXO blockchains, nonces are emulated through transaction chaining. - **Reissuance and Nullification:** Transactions can be reissued with different fees, or nullified by consuming the nonce with a minimal-fee transaction. - **Execution Proofs:** FDC attestation proofs verify whether a payment was executed as expected, enabling protocols to automatically mitigate failed payments. ### Flare Data Connector (FDC) The TEE-based FDC achieves fast attestation by issuing instruction events containing attestation requests. Data providers parse these requests, perform attestations using their existing data provision capabilities, and augment instructions with attestation responses. Each TEE machine that receives a threshold weight of signatures from data providers and cosigners signs the attestation response with its TEE identity key. Since TEE machine identities are verified on-chain during registration, their signatures serve as proof of data provider consensus usable within smart contracts. --- ## Private Key Extension Build and deploy a **Trusted Execution Environment (TEE) extension** that securely stores a private key and signs arbitrary messages. This guide walks through every step — from writing the smart contract and extension handler to deploying on Coston2 and running an end-to-end test. The code for this example is available on [GitHub](https://github.com/flare-foundation/fce-sign). :::info[New to Flare TEE?] A TEE extension is an offchain program that runs inside a Trusted Execution Environment. It receives **instructions** from onchain transactions, processes them in a secure enclave, and writes results back onchain. The TEE framework handles attestation, key management, and message routing — you only write the business logic. ::: ## Overview The Private Key Manager extension demonstrates the core TEE workflow: 1. A user sends an Elliptic Curve Integrated Encryption Scheme (ECIES) encrypted private key onchain via the `InstructionSender` contract. 2. The TEE extension decrypts and stores the key inside the secure enclave. 3. A user sends a `sign` instruction with an arbitrary message. 4. The TEE extension signs the message with the stored key and returns the signature onchain. We will build this in three parts: the **onchain contract** that sends instructions, the **offchain handler** that processes them, and the **deployment tooling** that ties everything together. ## Architecture The extension stack consists of three components running as Docker services: - **`extension-tee`:** Your extension code (Go, Python, or TypeScript). Receives decoded instructions from the proxy and returns results. - **`ext-proxy`:** The TEE extension proxy. Watches the chain for new instructions targeting your extension, forwards them to your handler, and submits results back onchain. - **`redis`:** In-memory store used by the proxy for internal state. The tunnel ([Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) or [ngrok](https://ngrok.com/)) exposes the proxy's external port so that other TEE nodes on the network can reach your extension for attestation and availability checks. ## Prerequisites Before you begin, make sure you have the following installed: - [Docker](https://docs.docker.com/get-docker/) and Docker Compose. - [Foundry](https://book.getfoundry.sh/getting-started/installation) for contract compilation and verification. - [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) to expose a local port to the internet (no account required), or [ngrok](https://ngrok.com/) (requires sign-up). - A funded Coston2 wallet with C2FLR for gas and TEE registration fees — use the [Coston2 faucet](https://faucet.flare.network/coston2). - Language-specific requirements: - **Go**: Go >= 1.23 - **Python**: Python >= 3.10, pip - **TypeScript**: Node.js >= 18, npm ## Onchain Contract The `InstructionSender` contract is the onchain entry point. It interacts with two Flare system contracts: - **`TeeExtensionRegistry`:** Registers extensions and routes instructions to TEE machines - **`TeeMachineRegistry`:** Tracks registered TEE machines and provides random selection ### Contract Code ```solidity title="contract/InstructionSender.sol" // SPDX-License-Identifier: MIT pragma solidity >=0.7.6 <0.9; contract InstructionSender { ITeeExtensionRegistry public immutable teeExtensionRegistry; ITeeMachineRegistry public immutable teeMachineRegistry; uint256 public _extensionId; constructor( address _teeExtensionRegistry, address _teeMachineRegistry ) { teeExtensionRegistry = ITeeExtensionRegistry(_teeExtensionRegistry); teeMachineRegistry = ITeeMachineRegistry(_teeMachineRegistry); } ... } ``` :::note The constructor takes the addresses of the two Flare system contracts. These are already deployed on Coston2 — the deploy tooling reads their addresses from `config/coston2/deployed-addresses.json`. This is a temporary solution, because the Flare confidential compute is still in development. On release, both addresses will be available through the [`FlareContractRegistry`](/network/guides/flare-contracts-registry) contract. ::: ### Instruction Parameters Every instruction sent to a TEE extension uses the `TeeInstructionParams` struct: ```solidity struct TeeInstructionParams { bytes32 opType; bytes32 opCommand; bytes message; address[] cosigners; uint64 cosignersThreshold; address claimBackAddress; } ``` - **`opType` and `opCommand`:** Route the instruction to the correct handler in your extension. This example uses `opType = "KEY"` with two commands: `"UPDATE"` and `"SIGN"` - **`message`:** Arbitrary payload. For `updateKey`, this is the ECIES-encrypted private key. For `sign`, this is the message to sign - **`cosigners` and `cosignersThreshold`:** Optional multi-party signing (not used in this example) - **`claimBackAddress`:** Optional refund address (not used in this example) ### Sending Instructions The `updateKey` function encrypts a private key and sends it to the TEE: ```solidity title="contract/InstructionSender.sol" function updateKey(bytes calldata _encryptedKey) external payable returns (bytes32) { require(_extensionId != 0, "extension ID not set"); address[] memory teeIds = teeMachineRegistry.getRandomTeeIds(_extensionId, 1); ITeeExtensionRegistry.TeeInstructionParams memory params; params.opType = bytes32("KEY"); params.opCommand = bytes32("UPDATE"); params.message = _encryptedKey; return teeExtensionRegistry.sendInstructions{value: msg.value}(teeIds, params); } ``` The flow is: 1. Call `getRandomTeeIds` to select a random TEE machine registered for this extension. 2. Build the instruction parameters with the appropriate `opType` and `opCommand`. 3. Call `sendInstructions` on the `TeeExtensionRegistry`, forwarding the fee as `msg.value`. The `sign` function follows the same pattern with `opCommand = "SIGN"`: ```solidity title="contract/InstructionSender.sol" function sign(bytes calldata _message) external payable returns (bytes32) { require(_extensionId != 0, "extension ID not set"); address[] memory teeIds = teeMachineRegistry.getRandomTeeIds(_extensionId, 1); ITeeExtensionRegistry.TeeInstructionParams memory params; params.opType = bytes32("KEY"); params.opCommand = bytes32("SIGN"); params.message = _message; return teeExtensionRegistry.sendInstructions{value: msg.value}(teeIds, params); } ``` Both functions return a `bytes32` instruction ID that can be used to track the instruction's lifecycle. :::tip[Customizing the contract] When building your own extension, change the `opType` and `opCommand` constants to match your use case. The same constants must appear in both the Solidity contract and your offchain handler code. ::: ## Offchain Handler The offchain handler is where your extension's business logic lives. The TEE framework calls your registered handler functions whenever a matching instruction arrives from the chain. This example is available in three languages. Each implementation follows the same structure: | Directory | Handler file | Config file | | --------------------- | ------------- | ----------- | | `go/internal/app/` | `handlers.go` | `config.go` | | `python/app/` | `handlers.py` | `config.py` | | `typescript/src/app/` | `handlers.ts` | `config.ts` | ### Handler registration Each handler is registered with the framework by matching an `opType`/`opCommand` pair. Here is the Go implementation: ```go title="go/internal/app/handlers.go" func Register(f *base.Framework) { f.Handle(OpTypeKey, OpCommandUpdate, handleKeyUpdate) f.Handle(OpTypeKey, OpCommandSign, handleKeySign) } ``` Where the constants match the Solidity contract: ```go title="go/internal/app/config.go" const ( OpTypeKey = "KEY" OpCommandUpdate = "UPDATE" OpCommandSign = "SIGN" ) ``` ### Handler signature Every handler receives the hex-encoded `originalMessage` from the onchain instruction and returns three values: ```go func myHandler(msg string) (data *string, status int, err error) ``` - **`msg`:** Hex-encoded message payload from the instruction - **`data`:** Hex-encoded return data (written back onchain), or `nil` if no data - **`status`:** `0` = error, `1` = success, `>=2` = pending - **`err`:** Go error if the handler failed ### The `updateKey` Handler The `handleKeyUpdate` handler decrypts an ECIES-encrypted private key and stores it in memory: ```go title="go/internal/app/handlers.go" func handleKeyUpdate(msg string) (data *string, status int, err error) { // Hex-decode the message to get raw ECIES ciphertext bytes ciphertext, hexErr := base.HexToBytes(msg) if hexErr != nil { return nil, 0, fmt.Errorf("invalid hex in originalMessage: %v", hexErr) } // Decrypt via the TEE node's /decrypt endpoint keyBytes, decryptErr := decryptViaNode(ciphertext) if decryptErr != nil { return nil, 0, fmt.Errorf("decryption failed: %v", decryptErr) } // Parse and store the private key privKey, parseErr := parseSecp256k1PrivateKey(keyBytes) if parseErr != nil { return nil, 0, fmt.Errorf("invalid private key: %v", parseErr) } privateKey = privKey return nil, 1, nil } ``` The decryption uses the TEE node's built-in `/decrypt` endpoint. The caller ECIES-encrypts the private key using the TEE's public key (fetched from the proxy's `/info` endpoint) before sending it onchain. This ensures the private key is never visible in plaintext onchain. ### The `sign` Handler The `handleKeySign` handler signs an arbitrary message with the stored private key: ```go title="go/internal/app/handlers.go" func handleKeySign(msg string) (data *string, status int, err error) { if privateKey == nil { return nil, 0, fmt.Errorf("no private key stored") } msgBytes, hexErr := base.HexToBytes(msg) if hexErr != nil { return nil, 0, fmt.Errorf("invalid hex in originalMessage: %v", hexErr) } sig, signErr := signECDSA(privateKey, msgBytes) if signErr != nil { return nil, 0, fmt.Errorf("signing failed: %v", signErr) } encoded, abiErr := abiEncodeTwo(msgBytes, sig) if abiErr != nil { return nil, 0, fmt.Errorf("ABI encoding failed: %v", abiErr) } dataHex := base.BytesToHex(encoded) return &dataHex, 1, nil } ``` The result is ABI-encoded as `(bytes, bytes)` — the original message and the ECDSA signature — and returned as hex. The proxy writes this data back onchain. ### Framework Utilities The `base/` package in each language provides common utilities so you can focus on business logic: | Utility | Description | | --------------------------- | ------------------------------------ | | `HexToBytes` / `BytesToHex` | Hex encoding and decoding | | `Keccak256` | Keccak-256 hashing | | `Framework` | Handler registration and HTTP server | :::warning The files in `base/` are framework infrastructure. You should not need to modify them when building your own extension. ::: ## Project Structure ``` sign/ ├── contract/ # Solidity contracts (shared across implementations) │ ├── InstructionSender.sol │ └── interface/ │ ├── ITeeExtensionRegistry.sol │ └── ITeeMachineRegistry.sol ├── config/ │ ├── proxy/ # Extension proxy configuration │ │ └── extension_proxy.toml.example │ └── coston2/ # Deployed contract addresses │ └── deployed-addresses.json ├── go/ # Go implementation │ ├── internal/app/ # Your business logic (modify these) │ ├── internal/base/ # Framework infrastructure (do not modify) │ └── tools/ # Deployment and testing tools ├── python/ # Python implementation │ ├── app/ # Your business logic (modify these) │ ├── base/ # Framework infrastructure (do not modify) │ └── tools/ # Deployment and testing tools ├── typescript/ # TypeScript implementation │ ├── src/app/ # Your business logic (modify these) │ ├── src/base/ # Framework infrastructure (do not modify) │ └── tools/ # Deployment and testing tools ├── proxy/ # Extension proxy (Docker build context) ├── docker-compose.yaml ├── .env.example └── README.md ``` ## Deploying and Testing on Coston2 We will now walk through deploying the extension end-to-end on the [Coston2 testnet](/network/overview#configuration). Each step builds on the previous one. ### Step 0: Configure Environment Create the environment file and proxy configuration from the provided examples: ```bash cp .env.example .env cp config/proxy/extension_proxy.toml.example config/proxy/extension_proxy.toml ``` Edit `.env` and fill in the required values: ```bash title=".env" # Language implementation to run (go, python, or typescript) LANGUAGE=go # Funded Coston2 private key (hex, no 0x prefix) PRIVATE_KEY="" # Initial owner address (derived from PRIVATE_KEY) INITIAL_OWNER="0x" ``` Edit `config/proxy/extension_proxy.toml` and fill in the DB credentials for the Coston2 C-chain indexer: ```toml title="config/proxy/extension_proxy.toml" [db] host = "35.233.36.8" port = 3306 database = "indexer" username = "" password = "" ``` :::info[Running a local indexer] If you prefer to run your own local indexer instead of connecting to the shared one, see `config/indexer/` and swap the `[db]` section in the proxy config. A commented example is included in the file. ::: ### Step 1: Deploy the InstructionSender Contract Deploy the `InstructionSender` contract to Coston2. The deploy tool reads the `TeeExtensionRegistry` and `TeeMachineRegistry` addresses from `config/coston2/deployed-addresses.json` and passes them to the constructor. Choose your language: **Go:** ```bash cd go/tools go run ./cmd/deploy-contract ``` **Python:** ```bash cd python/tools python -m cmd.deploy_contract ``` **TypeScript:** ```bash cd typescript/tools npm run build && npm run deploy-contract ``` The tool automatically verifies the contract source on the [Coston2 block explorer](https://coston2-explorer.flare.network/). Pass `--no-verify` to skip verification. Save the printed contract address in `.env`: ```bash title=".env" INSTRUCTION_SENDER="0x" ``` ### Step 2: Register the Extension Register your extension with the `TeeExtensionRegistry`. This tells the TEE framework that your `InstructionSender` contract is the authorized instruction sender for a new extension. **Go:** ```bash cd go/tools go run ./cmd/register-extension ``` **Python:** ```bash cd python/tools source ../../.env python -m cmd.register_extension $INSTRUCTION_SENDER ``` **TypeScript:** ```bash cd typescript/tools npm run register-extension ``` Save the printed extension ID in `.env`: ```bash title=".env" EXTENSION_ID="0x<64-hex-chars>" ``` ### Step 3: Start the Extension Stack Build and start the Docker Compose stack. The `LANGUAGE` variable in `.env` determines which implementation runs: ```bash docker compose build docker compose up -d ``` Wait for the proxy to become healthy: ```bash until curl -sf http://localhost:6676/info >/dev/null 2>&1; do sleep 2; done echo "Extension proxy is ready" ``` :::tip[Switching languages] If you change the `LANGUAGE` variable in `.env`, rebuild the stack with `docker compose build` before starting it again. ::: ### Step 4: Start the Tunnel In a separate terminal, expose the extension proxy's external port (6676) to the internet: ```bash # Using cloudflared (no account required): cloudflared tunnel --url http://localhost:6676 # Or using ngrok: ngrok http 6676 ``` Note the public HTTPS URL printed by the tunnel tool and save it in `.env`: ```bash title=".env" TUNNEL_URL="https://" ``` :::warning The tunnel must stay running for the entire session. If your computer sleeps or the tunnel restarts, the URL might change. Update `TUNNEL_URL` in `.env`, restart the Docker stack, and re-run steps 5-6. ::: ### Step 5: Add TEE Version Register the code version (hash) and platform of your TEE with the `TeeVersionManager`. This step tells the network which software version your TEE is running. **Go:** ```bash cd go/tools go run ./cmd/allow-tee-version -p http://localhost:6676 ``` **Python:** ```bash cd python/tools python -m cmd.allow_tee_version -p http://localhost:6676 ``` **TypeScript:** ```bash cd typescript/tools npx ts-node src/cmd/allow-tee-version.ts -p http://localhost:6676 ``` ### Step 6: Register the TEE Machine Register your TEE machine with the `TeeMachineRegistry`. This is a multi-step process: 1. **Pre-registration** - Announces the TEE machine to the network. 2. **Attestation** - The TEE proves it is running in a genuine secure enclave. 3. **Production** - After passing the FDC availability check, the TEE becomes active. Make sure `TUNNEL_URL` is set correctly in `.env`, then run: **Go:** ```bash cd go/tools go run ./cmd/register-tee -p http://localhost:6676 -l ``` **Python:** ```bash cd python/tools python -m cmd.register_tee -p http://localhost:6676 -l ``` **TypeScript:** ```bash cd typescript/tools npm run register-tee -- -p http://localhost:6676 -l ``` The `-l` flag enables **local/test mode**, which uses a test attestation token instead of a real [Google Cloud Platform JSON Web Token (JWT)](https://docs.cloud.google.com/api-gateway/docs/authenticating-users-jwt). This is required when running outside of an actual TEE enclave. The `-p` flag specifies the proxy URL used for the FDC availability check. It defaults to `https://tee-proxy-coston2-1.flare.rocks` (the Coston2 public TEE proxy). ### Step 7: Run the End-to-end Test With the full stack running and the TEE machine registered, run the end-to-end test to verify everything works. Make sure `INSTRUCTION_SENDER` and `TUNNEL_URL` are set correctly in `.env`. **Go:** ```bash cd go/tools go run ./cmd/run-test -p http://localhost:6676 ``` **Python:** ```bash cd python/tools python -m cmd.run_test -p http://localhost:6676 ``` **TypeScript:** ```bash cd typescript/tools npm run run-test -- -p http://localhost:6676 ``` The test performs the following sequence: 1. Calls `setExtensionId()` on the `InstructionSender` contract to discover and store the extension ID. 2. Fetches the TEE's public key from the proxy's `/info` endpoint. 3. ECIES-encrypts a test private key using the TEE's public key. 4. Sends an `updateKey` instruction onchain with the encrypted key. 5. Waits for the TEE to process the instruction and store the key. 6. Sends a `sign` instruction onchain with a test message. 7. Verifies the returned ECDSA signature matches the test private key. If the test passes, your extension is fully operational. ## Port reference | Service | Container port | Host port | | ------------------ | -------------- | --------- | | ext-proxy internal | 6663 | 6675 | | ext-proxy external | 6664 | 6676 | | redis | 6379 | 6383 | The tunnel exposes host port **6676** (ext-proxy external) to the internet. The internal port (6675) is used for communication between the extension container and the proxy within the Docker network. ## Building your own extension To create your own TEE extension using this template: 1. **Clone the repository** and pick a language directory (`go/`, `python/`, or `typescript/`). 2. **Define your instruction types** - Choose `opType` and `opCommand` constants that describe your extension's operations. 3. **Modify `InstructionSender.sol`** - Update the contract functions to use your new constants and accept the appropriate parameters. 4. **Write your handlers** - Implement handler functions in the `app/` directory that process your instructions. 5. **Register handlers** - Wire up your handlers with the framework using `f.Handle(opType, opCommand, myHandler)`. 6. **Deploy and test** - Follow the steps in this guide to deploy your contract, register the extension, and verify it works. :::tip[What to change vs. what to keep] Only modify files in `app/` (your business logic) and `contract/InstructionSender.sol` (your onchain interface). The files in `base/` are framework infrastructure and should not need changes. ::: ## Troubleshooting **Proxy won't start or DB sync error** The proxy needs a synced C-chain indexer database. Check the proxy logs and verify the DB credentials in `config/proxy/extension_proxy.toml`: ```bash docker compose logs ext-proxy ``` **Transaction reverts** Ensure your wallet has enough C2FLR for gas and fees. Fund it with the [Coston2 faucet](https://faucet.flare.network/coston2). The `TeeFeeCalculator` contract determines the required fee for each operation. If you get a `FeeTooLow` revert, increase `FEE_WEI` in `.env`. **TEE registration times out** Try restarting the proxy — it may have missed a signing policy round: ```bash docker compose restart ext-proxy ``` If that doesn't help, the FDC attestation flow requires active relay providers on Coston2. If no relay infrastructure is running, the availability check won't complete. **Tunnel URL changed** If your tunnel restarts and the URL changes: 1. Update `TUNNEL_URL` in `.env`. 2. Restart the Docker stack: ```bash docker compose down && docker compose up -d ``` 3. Re-run steps 5-6 (allow-tee-version + register-tee) to register a new TEE machine with the new URL. ## Cleanup **Stop the Docker stack** ```bash docker compose down ``` This stops and removes all containers (redis, ext-proxy, extension-tee). **Full reset** To completely reset and start from scratch: ```bash # Remove built images (forces rebuild) docker compose down --rmi local # Clear environment state rm -f .env config/proxy/extension_proxy.toml ``` After a full reset, start again from [Step 0](#step-0-configure-environment). :::info onchain state (deployed contracts, registered extensions, registered TEEs) cannot be reset. Each fresh start deploys a new `InstructionSender` contract and registers a new extension. This is fine for testing — Coston2 is a testnet. ::: --- ## Check System Requirements :::info[Node Types and Load] These requirements represent a baseline for running a stable go-flare node. - **RPC Nodes:** Adjust CPU and RAM upwards if you expect to serve a high volume of RPC requests. - **Validator Nodes:** While base requirements are similar, validators have high uptime and performance demands. Consider higher-end specifications within the recommended range for validators. - **Flare Entity:** Running the additional FSP components alongside a validator requires more resources, detailed here. ::: ## Hardware | | **Requirement** | | :------------------------ | :------------------------------ | | **CPU** | 4 cores | | **RAM** | 16 GB | | **Disk space (pruned)** | 1 TB SSD | | **Disk space (archival)** | 5.7 TB SSD | | **Disk growth** | 30 GB/month | | **Disk speed** | 1200 MB/s read + 600 MB/s write | | | **Requirement** | | :------------------------ | :------------------------------ | | **CPU** | 4 cores | | **RAM** | 16 GB | | **Disk space (pruned)** | 250 GB SSD | | **Disk space (archival)** | 1.2 TB SSD | | **Disk growth** | 5 GB/month | | **Disk speed** | 1200 MB/s read + 600 MB/s write | | | **Requirement** | | :------------------------ | :------------------------------ | | **CPU** | 4 cores | | **RAM** | 16 GB | | **Disk space (pruned)** | 2.7 TB SSD | | **Disk space (archival)** | 11.2 TB SSD | | **Disk growth** | 120 GB/month | | **Disk speed** | 1200 MB/s read + 600 MB/s write | | | **Requirement** | | :------------------------ | :------------------------------ | | **CPU** | 4 cores | | **RAM** | 16 GB | | **Disk space (pruned)** | 300 GB SSD | | **Disk space (archival)** | 1.6 TB SSD | | **Disk growth** | 11 GB/month | | **Disk speed** | 1200 MB/s read + 600 MB/s write |
Flare Entity requirements. To deploy all [Flare Entity](/run-node/flare-entity) components on a single HW instance: | | **Requirement** | |:--------------------------|:----------------| | **CPU** | 16 cores | | **RAM** | 64 GB | | **Disk space** | 4 TB SSD | | | **Requirement** | |:--------------------------|:----------------| | **CPU** | 8 cores | | **RAM** | 16 GB | | **Disk space** | 100 GB SSD | | | **Requirement** | |:--------------------------|:-----------------| | **CPU** | 16 cores | | **RAM** | 64 GB | | **Disk space** | 4 TB SSD | | | **Requirement** | |:--------------------------|:----------------| | **CPU** | 8 cores | | **RAM** | 16 GB | | **Disk space** | 100 GB SSD | It is possible to split the FSP deployment across multiple instances, but this deviation from the recommended deployment configuration could introduce additional friction when updating.
## Operating System While go-flare might run on various operating systems, production nodes are most commonly and reliably run on Linux, with distributions such as [Ubuntu LTS](https://ubuntu.com) (e.g., 22.04, 24.04) being highly recommended and the most tested environment. ## Networking Stable connectivity is essential. Ensure at least **40 Mbps download / 10 Mbps upload** sustained speed (note: initial sync uses significant bandwidth). If behind a firewall or NAT, configure port forwarding for these default TCP ports: - **Port `9650` (API):** The default API port; **secure external API access** using firewall rules or a reverse proxy if needed. - **Port `9651` (P2P/Staking):** Required by **all nodes** for P2P peer discovery/communication. Additionally used by validators for P-Chain consensus communication. Must be publicly reachable. A static public IP address is highly recommended for validators to ensure consistent uptime and reliability, potentially impacting rewards. --- ## Run Node from Source This guide will walk you through deploying an RPC node from the [go-flare](https://github.com/flare-foundation/go-flare) source code. Building from source gives you more control over the build process and access to the very latest code, but requires managing dependencies manually. Make sure to check the [hardware requirements](/run-node/system-requirements) before proceeding. ## Prerequisites - A machine meeting the [minimum hardware requirements](/run-node/system-requirements). - [Go](https://go.dev/doc/install) (`1.21.8`) - [git](https://git-scm.com/install/) - [GCC](https://gcc.gnu.org/install/) - [jq](https://stedolan.github.io/jq/download/) - [rsync](https://github.com/RsyncProject/rsync) - [cURL](https://curl.se/download.html) - [Ubuntu](https://ubuntu.com) (`>=24.10`) ## Build the binary 1. Retrieve the source code from the latest stable release: ```bash # 1. Find the latest stable release tag from: # https://github.com/flare-foundation/go-flare/releases # 2. Set the tag name in the variable below: LATEST_TAG="vX.Y.Z" # <-- REPLACE vX.Y.Z WITH THE ACTUAL LATEST TAG e.g. v1.12.0 ``` 2. Clone and build the binary: ```bash git clone --branch ${LATEST_TAG} https://github.com/flare-foundation/go-flare.git cd go-flare/avalanchego ./scripts/build.sh ``` The resulting executable will be stored in `build/avalanchego`. 3. Verify the installation by running: ```bash go test $(go list ./... | grep -v /tests/) # avalanchego unit tests cd ../coreth go test ./... # coreth unit tests cd - ``` ## Run the node 1. The following command is the simplest way to quickly get your node up and running. The next section explains the parameters used here, along with additional parameters you may wish to configure. ```bash ./build/avalanchego --network-id=flare \ --http-host= \ --http-allowed-hosts="*" \ --bootstrap-ips="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeIP"}' -H 'content-type:application/json;' https://flare-bootstrap.flare.network/ext/info | jq -r '.result.ip')" \ --bootstrap-ids="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeID"}' -H 'content-type:application/json;' https://flare-bootstrap.flare.network/ext/info | jq -r '.result.nodeID')" ``` ```bash ./build/avalanchego --network-id=costwo \ --http-host= \ --http-allowed-hosts="*" \ --bootstrap-ips="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeIP"}' -H 'content-type:application/json;' https://coston2-bootstrap.flare.network/ext/info | jq -r '.result.ip')" \ --bootstrap-ids="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeID"}' -H 'content-type:application/json;' https://coston2-bootstrap.flare.network/ext/info | jq -r '.result.nodeID')" ``` ```bash ./build/avalanchego --network-id=songbird \ --http-host= \ --http-allowed-hosts="*" \ --bootstrap-ips="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeIP"}' -H 'content-type:application/json;' https://songbird-bootstrap.flare.network/ext/info | jq -r '.result.ip')" \ --bootstrap-ids="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeID"}' -H 'content-type:application/json;' https://songbird-bootstrap.flare.network/ext/info | jq -r '.result.nodeID')" ``` ```bash ./build/avalanchego --network-id=coston \ --http-host= \ --http-allowed-hosts="*" \ --bootstrap-ips="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeIP"}' -H 'content-type:application/json;' https://coston-bootstrap.flare.network/ext/info | jq -r '.result.ip')" \ --bootstrap-ids="$(curl -m 10 -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeID"}' -H 'content-type:application/json;' https://coston-bootstrap.flare.network/ext/info | jq -r '.result.nodeID')" ``` 2. After a lot of log messages the node should start synchronizing with the network, which might take anywhere from a **few hours to a few days** depending on the chosen network and hardware specification. Node syncing can be stopped at any time. Use the same command to resume the node syncing from where it left off. You will know your node is fully booted and accepting transactions when the output of this command contain the field `"healthy" : true` in the returned JSON object: ```bash curl http://127.0.0.1:9650/ext/health | jq ``` 3. (Optional) If you plan to [register your node as a validator](/run-node/register-validator). Make sure to copy the staking keys to a persistent directory outside the default location. Consider also backing up these keys to a secure vault of your choice. This is important for ensuring that your staking keys are not lost if the node is restarted or updated. ```bash # Create a dedicated directory sudo mkdir -p /opt/flare/staking # Move your keys sudo cp -r ~/.avalanchego/staking/* /opt/flare/staking/ ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/coston2/staking # Move your keys sudo cp -r ~/.avalanchego/staking/* /opt/coston2/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/songbird/staking # Move your keys sudo cp -r ~/.avalanchego/staking/* /opt/songbird/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/coston/staking # Move your keys sudo cp -r ~/.avalanchego/staking/* /opt/coston/staking ``` :::tip[Node stuck when bootstrapping?] If the node gets stuck during bootstrap (or it takes far longer than the estimates given above), try adding the parameter `--bootstrap-retry-enabled=false` when running the node. ::: ### CLI parameters These are some of the most relevant CLI parameters you can use. Read more about them in the [Avalanche documentation](https://build.avax.network/docs/nodes/configure/configs-flags). {/* prettier-ignore */} - [`--bootstrap-ips`](https://build.avax.network/docs/nodes/configure/configs-flags#--bootstrap-ips-string), [`--bootstrap-ids`](https://build.avax.network/docs/nodes/configure/configs-flags#--bootstrap-ids-string): IP address and node ID of the peer used to connect to the rest of the network for bootstrapping. In the run command above, the bootstrap details are programmatically retrieved from the Flare bootstrap nodes upon startup. This is the recommended approach as the bootstrap node's IP and ID can rotate. - [`--http-host`](https://build.avax.network/docs/nodes/configure/configs-flags#--http-host-string): Use `--http-host=` (empty) to allow connections from other machines. Otherwise, only connections from `localhost` are accepted. - [`--http-port`](https://build.avax.network/docs/nodes/configure/configs-flags#--http-port-int): The port through which the node will listen to API requests. The default value is `9650`. - [`--http-allowed-hosts`](https://build.avax.network/docs/nodes/configure/configs-flags#--http-allowed-hosts-string): Use `--http-allowed-hosts="*"` to allow connections from any machine. Otherwise, only connections from `localhost` are accepted. - [`--staking-port`](https://build.avax.network/docs/nodes/configure/configs-flags#--staking-port-int): The port through which the network peers will connect to this node externally. Having this port accessible from the internet is required for correct node operation. The default value is `9651`. - [`--db-dir`](https://build.avax.network/docs/nodes/configure/configs-flags#--db-dir-string-file-path): Directory where the database is stored, defaults to `~/.avalanchego/db`. Make sure to use a disk with enough space as recommended in the [hardware requirements](/run-node/system-requirements) page. For example, you can use this option to store the database on an external drive. - [`--chain-config-dir`](https://build.avax.network/docs/nodes/configure/configs-flags#--chain-config-dir-string): Optional JSON configuration file for using non-default values. #### Sample JSON configuration: Most of the C-Chain configuration options can be found in the [Avalanche C-Chain Configuration docs](https://build.avax.network/docs/nodes/chain-configs/avalanche-l1s/subnet-evm). Note that the default values are overridden only if specified in the given config file. Only provide values which differ from the defaults to avoid issues with future updates. ```json title="/C/config.json" { "snowman-api-enabled": false, "coreth-admin-api-enabled": false, "eth-apis": [ "eth", "eth-filter", "net", "web3", "internal-eth", "internal-blockchain", "internal-transaction" ], "rpc-gas-cap": 50000000, "rpc-tx-fee-cap": 100, "pruning-enabled": true, // Set to false for archival nodes, but requires more disk space "local-txs-enabled": false, "api-max-duration": 0, "api-max-blocks-per-request": 0, "allow-unfinalized-queries": false, "allow-unprotected-txs": false, "remote-tx-gossip-only-enabled": false, "log-level": "info" } ``` ## Update the node To update your node to a newer version of go-flare: 1. Stop the running node (e.g., using `Ctrl+C` if running in the foreground, or via your service manager like `systemd`). 2. Navigate to the source directory: ```bash cd /path/to/go-flare ``` 3. Fetch the latest changes and checkout the new version: ```bash git fetch origin # 1. Find the latest stable release tag from: # https://github.com/flare-foundation/go-flare/releases # 2. Set the tag name in the variable below: LATEST_TAG="vX.Y.Z" # <-- REPLACE vX.Y.Z WITH THE ACTUAL LATEST TAG e.g. v1.12.0 git checkout ${LATEST_TAG} ``` 4. Rebuild the binary: ```bash cd go-flare/avalanchego ./scripts/build.sh ``` 5. Restart the node using the same run command and parameters as [before](#run-the-node). ## Troubleshooting If you encounter issues, here are some common troubleshooting steps. - **Check Peer Count:** A node needs sufficient peers (typically at least 16) to sync and operate correctly. Use the health endpoint to check your connection count. ```bash # Check the "connectedPeers" value in the output curl -s http://127.0.0.1:9650/ext/health | jq -r '.checks.network.message' ``` - **Monitor Disk Space:** If your node stops syncing, ensure the database location (`/mnt/db`) has enough free space. Run `df -h /mnt/db` to check. - **Restart on Errors:** If the node becomes unhealthy or you see persistent `GetAcceptedFrontier` errors in the logs, a simple restart can often resolve the issue. --- ## Run Node using Docker This guide will walk you through deploying an RPC node using the [go-flare](https://hub.docker.com/r/flarefoundation/go-flare) images on Docker Hub. ## Prerequisites - A machine meeting the [minimum hardware requirements](/run-node/system-requirements). - [Docker Engine](https://docs.docker.com/engine/install/) - [jq](https://stedolan.github.io/jq/download/) :::tip[Docker without sudo] To run `docker` commands without `sudo`, add your user to the `docker` group: ```bash sudo usermod -a -G docker $USER ``` Log out and log back in or restart your system for the changes to take effect. ::: ## Configure the machine First, you'll prepare your host machine by setting up a dedicated disk for blockchain data and creating necessary configuration directories. ### Disk setup Blockchain data requires significant storage, so it's best practice to use a separate, dedicated disk. These steps will guide you through formatting and mounting it. 1. **Define environment variables:** To prevent errors, define the device name and your current user as variables. This avoids having to manually replace placeholders in every command. ```bash # Identify your disk with `lsblk`, then set it here. # ⚠️ DANGER: The device you choose will be completely erased! DISK_DEVICE="/dev/sdb" # Set the user and group for directory ownership. DOCKER_USER=$(whoami) ``` 2. **Format and mount the disk:** The following commands will create a directory at `/mnt/db`, format your disk, mount it, and grant your user ownership. :::warning[Data Loss] The `mkfs.ext4` command will erase all data on the specified device (`$DISK_DEVICE`). Double-check that you've selected the correct disk and backed up any important data. ::: ```bash # See a list of available block devices to identify the correct one. lsblk # Create mount point and set permissions sudo mkdir -p /mnt/db sudo chown -R ${DOCKER_USER}:${DOCKER_USER} /mnt/db # Format the disk (EXT4 filesystem) sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard ${DISK_DEVICE} # Mount the disk sudo mount ${DISK_DEVICE} /mnt/db ``` 3. **Ensure persistent mounting:** To ensure the disk is automatically mounted after a system reboot, add an entry to `/etc/fstab`. Using the disk's `UUID` is a robust method because device names like `/dev/sdb` can sometimes change. ```bash # Back up your current fstab file sudo cp /etc/fstab /etc/fstab.backup # Get the UUID of your newly formatted disk DISK_UUID=$(sudo blkid -o value -s UUID ${DISK_DEVICE}) # Add the new entry to /etc/fstab echo "UUID=${DISK_UUID} /mnt/db ext4 discard,defaults 0 2" | sudo tee -a /etc/fstab # Verify the mount sudo mount -a lsblk | grep /mnt/db ``` ### Configuration File and Logs Directory Setup Create directories on your host machine to store the node's configuration file and logs. Mounting these allows you to manage them directly without entering the container. 1. **Create directories:** ```bash sudo mkdir -p /opt/flare/conf /opt/flare/logs sudo chown -R ${DOCKER_USER}:${DOCKER_USER} /opt/flare ``` 2. **Create configuration file:** Create a `config.json` file. This example provides a standard configuration for a public RPC node. Most of the C-Chain configuration options can be found in the [Avalanche C-Chain Configuration docs](https://build.avax.network/docs/nodes/chain-configs/avalanche-l1s/subnet-evm). Note that the default values are overridden only if specified in the given config file. Only provide values which differ from the defaults to avoid issues with future updates. ```json title="/opt/flare/conf/config.json" { "snowman-api-enabled": false, "coreth-admin-api-enabled": false, "eth-apis": [ "eth", "eth-filter", "net", "web3", "internal-eth", "internal-blockchain", "internal-transaction" ], "rpc-gas-cap": 50000000, "rpc-tx-fee-cap": 100, "pruning-enabled": true, "local-txs-enabled": false, "api-max-duration": 0, "api-max-blocks-per-request": 0, "allow-unfinalized-queries": false, "allow-unprotected-txs": false, "remote-tx-gossip-only-enabled": false, "log-level": "info" } ``` ## Run the node You can run your node using either the [Docker CLI](#using-docker-cli) directly or with [Docker Compose](#using-docker-compose) for easier management. ### Using Docker CLI 1. **Get the latest stable version tag:** ```bash # 1. Find the latest stable release tag from: # https://hub.docker.com/r/flarefoundation/go-flare/tags # 2. Set the tag name in the variable below (only use versioned tags): LATEST_TAG="vX.Y.Z" # <-- REPLACE vX.Y.Z WITH THE ACTUAL LATEST TAG e.g. v1.12.0 ``` 2. **Start the container:** ```bash docker run -d --name flare-node \ -e NETWORK_ID="flare" \ -e AUTOCONFIGURE_BOOTSTRAP="1" \ -e AUTOCONFIGURE_PUBLIC_IP="1" \ -e AUTOCONFIGURE_BOOTSTRAP_ENDPOINT="https://flare-bootstrap.flare.network/ext/info" \ -v /mnt/db:/app/db -v /opt/flare/conf:/app/conf/C \ -v /opt/flare/logs:/app/logs \ -p 0.0.0.0:9650:9650 \ -p 0.0.0.0:9651:9651 \ flarefoundation/go-flare:${LATEST_TAG} ``` Confirm your container is running and inspect that logs are printing: ```bash docker ps docker logs flare-node -f ``` ```bash docker run -d --name coston2-node \ -e NETWORK_ID="costwo" \ -e AUTOCONFIGURE_BOOTSTRAP="1" \ -e AUTOCONFIGURE_PUBLIC_IP="1" \ -e AUTOCONFIGURE_BOOTSTRAP_ENDPOINT="https://coston2-bootstrap.flare.network/ext/info" \ -v /mnt/db:/app/db -v /opt/flare/conf:/app/conf/C \ -v /opt/flare/logs:/app/logs \ -p 0.0.0.0:9650:9650 \ -p 0.0.0.0:9651:9651 \ flarefoundation/go-flare:${LATEST_TAG} ``` Confirm your container is running and inspect that logs are printing: ```bash docker ps docker logs coston2-node -f ``` ```bash docker run -d --name songbird-node \ -e NETWORK_ID="songbird" \ -e AUTOCONFIGURE_BOOTSTRAP="1" \ -e AUTOCONFIGURE_PUBLIC_IP="1" \ -e AUTOCONFIGURE_BOOTSTRAP_ENDPOINT="https://songbird-bootstrap.flare.network/ext/info" \ -v /mnt/db:/app/db -v /opt/flare/conf:/app/conf/C \ -v /opt/flare/logs:/app/logs \ -p 0.0.0.0:9650:9650 \ -p 0.0.0.0:9651:9651 \ flarefoundation/go-flare:${LATEST_TAG} ``` Confirm your container is running and inspect that logs are printing: ```bash docker ps docker logs songbird-node -f ``` ```bash docker run -d --name coston-node \ -e NETWORK_ID="coston" \ -e AUTOCONFIGURE_BOOTSTRAP="1" \ -e AUTOCONFIGURE_PUBLIC_IP="1" \ -e AUTOCONFIGURE_BOOTSTRAP_ENDPOINT="https://coston-bootstrap.flare.network/ext/info" \ -v /mnt/db:/app/db -v /opt/flare/conf:/app/conf/C \ -v /opt/flare/logs:/app/logs \ -p 0.0.0.0:9650:9650 \ -p 0.0.0.0:9651:9651 \ flarefoundation/go-flare:${LATEST_TAG} ``` Confirm your container is running and inspect that logs are printing: ```bash docker ps docker logs coston-node -f ``` 3. **Check health:** Once the node is running, you can check its health. It will report `"healthy": true` only after it has fully synced. ```bash # Press Ctrl+C to exit the logs, then run: curl http://localhost:9650/ext/health | jq ``` 4. **(Optional)** If you plan to [register your node as a validator](/run-node/register-validator). Make sure to copy the staking keys to a persistent directory outside the default location. Consider also backing up these keys to a secure vault of your choice. This is important for ensuring that your staking keys are not lost if the node is restarted or updated. ```bash # Create a dedicated directory sudo mkdir -p /opt/flare/staking # Move your keys docker cp flare-node:/root/.avalanchego/staking /opt/flare/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/coston2/staking # Move your keys docker cp coston2-node:/root/.avalanchego/staking /opt/coston2/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/songbird/staking # Move your keys docker cp songbird-node:/root/.avalanchego/staking /opt/songbird/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/coston/staking # Move your keys docker cp coston-node:/root/.avalanchego/staking /opt/coston/staking ```
Explanation of the CLI arguments. **Volumes Mounts** - `-v /mnt/db:/app/db`: Mount the local database directory to the default database directory of the container. - `-v /opt/flare/conf:/app/conf/C`: Mount the local configuration directory to the default location of `config.json`. - `-v /opt/flare/logs:/app/logs`: Mount the local logs directory to the workloads default logs directory. **Port Mappings** - `-p 0.0.0.0:9651:9651`: Mapping the container's peering port to your local machine so other peers can reach the node and allow it to gain peers on the network. - `-p 0.0.0.0:9650:9650`: Mapping the container's HTTP port to your local machine, enabling the querying of the containerized RPC node's HTTP port via your local machine's IP and port. :::warning Only expose port **9650** with `0.0.0.0` to make your node's RPC endpoint publicly accessible. If you need external access, it is critical to implement firewall rules that restrict access to specific, trusted IP addresses. ::: **Environment Variables** - `-e AUTOCONFIGURE_BOOTSTRAP="1"`: Retrieves the bootstrap endpoints Node-IP and Node-ID automatically. - `-e NETWORK_ID=""`: Sets the correct network ID, either `flare`, `costwo`, `songbird`, or `coston`. - `-e AUTOCONFIGURE_PUBLIC_IP="1"`: Retrieves your local machine's public IP automatically. - `-e AUTOCONFIGURE_BOOTSTRAP_ENDPOINT="/ext/info"`: Defines the bootstrap endpoint used to initialize chain sync, a list of them is available on the [Network Configuration](/network/overview#configuration) page.
### Using Docker Compose Docker Compose simplifies node management by defining the entire configuration in a single YAML file. 1. **Create a Workspace:** In this guide the `docker-compose.yaml` file is created in `/opt/node` but the location is entirely up to you. ```bash sudo mkdir /opt/node sudo chown -R ${DOCKER_USER}:${DOCKER_USER} /opt/node ``` 2. **Create the `docker-compose.yaml` file:** ```yaml title="/opt/node/docker-compose.yaml" services: node: container_name: flare-node # 1. Find the latest stable release tag from: # https://hub.docker.com/r/flarefoundation/go-flare/tags # 2. Set the tag name in the variable below (only use versioned tags): image: flarefoundation/go-flare:vX.Y.Z # <-- REPLACE vX.Y.Z WITH THE ACTUAL LATEST TAG e.g. v1.12.0 restart: on-failure environment: - NETWORK_ID=flare - AUTOCONFIGURE_BOOTSTRAP=1 - AUTOCONFIGURE_PUBLIC_IP=1 - AUTOCONFIGURE_BOOTSTRAP_ENDPOINT=https://flare-bootstrap.flare.network/ext/info volumes: - /mnt/db:/app/db - /opt/flare/conf:/app/conf/C - /opt/flare/logs:/app/logs ports: - 0.0.0.0:9650:9650 - 0.0.0.0:9651:9651 ``` ```yaml title="/opt/node/docker-compose.yaml" services: node: container_name: coston2-node # 1. Find the latest stable release tag from: # https://hub.docker.com/r/flarefoundation/go-flare/tags # 2. Set the tag name in the variable below (only use versioned tags): image: flarefoundation/go-flare:vX.Y.Z # <-- REPLACE vX.Y.Z WITH THE ACTUAL LATEST TAG e.g. v1.12.0 restart: on-failure environment: - NETWORK_ID=costwo - AUTOCONFIGURE_BOOTSTRAP=1 - AUTOCONFIGURE_PUBLIC_IP=1 - AUTOCONFIGURE_BOOTSTRAP_ENDPOINT=https://coston2-bootstrap.flare.network/ext/info volumes: - /mnt/db:/app/db - /opt/flare/conf:/app/conf/C - /opt/flare/logs:/app/logs ports: - 0.0.0.0:9650:9650 - 0.0.0.0:9651:9651 ``` ```yaml title="/opt/node/docker-compose.yaml" services: node: container_name: songbird-node # 1. Find the latest stable release tag from: # https://hub.docker.com/r/flarefoundation/go-flare/tags # 2. Set the tag name in the variable below (only use versioned tags): image: flarefoundation/go-flare:vX.Y.Z # <-- REPLACE vX.Y.Z WITH THE ACTUAL LATEST TAG e.g. v1.12.0 restart: on-failure environment: - NETWORK_ID=songbird - AUTOCONFIGURE_BOOTSTRAP=1 - AUTOCONFIGURE_PUBLIC_IP=1 - AUTOCONFIGURE_BOOTSTRAP_ENDPOINT=https://songbird-bootstrap.flare.network/ext/info volumes: - /mnt/db:/app/db - /opt/flare/conf:/app/conf/C - /opt/flare/logs:/app/logs ports: - 0.0.0.0:9650:9650 - 0.0.0.0:9651:9651 ``` ```yaml title="/opt/node/docker-compose.yaml" services: node: container_name: coston-node # 1. Find the latest stable release tag from: # https://hub.docker.com/r/flarefoundation/go-flare/tags # 2. Set the tag name in the variable below (only use versioned tags): image: flarefoundation/go-flare:vX.Y.Z # <-- REPLACE vX.Y.Z WITH THE ACTUAL LATEST TAG e.g. v1.12.0 restart: on-failure environment: - NETWORK_ID=coston - AUTOCONFIGURE_BOOTSTRAP=1 - AUTOCONFIGURE_PUBLIC_IP=1 - AUTOCONFIGURE_BOOTSTRAP_ENDPOINT=https://coston-bootstrap.flare.network/ext/info volumes: - /mnt/db:/app/db - /opt/flare/conf:/app/conf/C - /opt/flare/logs:/app/logs ports: - 0.0.0.0:9650:9650 - 0.0.0.0:9651:9651 ``` 3. **Start the service:** Navigate to your workspace and launch the node in detached mode. ```bash cd /opt/node docker compose up -d ``` 4. **Monitor logs and check health:** Check the status of your service and follow its logs. ```bash docker ps docker compose logs -f ``` Check the health endpoint to confirm it's running and accessible. ```bash # Press Ctrl+C to exit the logs, then run: curl -s http://localhost:9650/ext/health | jq ``` 5. **(Optional)** If you plan to [register your node as a validator](/run-node/register-validator). Make sure to copy the staking keys to a persistent directory outside the default location. Consider also backing up these keys to a secure vault of your choice. This is important for ensuring that your staking keys are not lost if the node is restarted or updated. ```bash # Create a dedicated directory sudo mkdir -p /opt/flare/staking # Move your keys docker compose cp flare-node:/root/.avalanchego/staking /opt/flare/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/coston2/staking # Move your keys docker compose cp coston2-node:/root/.avalanchego/staking /opt/coston2/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/songbird/staking # Move your keys docker compose cp songbird-node:/root/.avalanchego/staking /opt/songbird/staking ``` ```bash # Create a dedicated directory sudo mkdir -p /opt/coston/staking # Move your keys docker compose cp coston-node:/root/.avalanchego/staking /opt/coston/staking ```
Additional configuration options There are several environment variables to adjust your workload at runtime. The example Docker and Docker Compose guides above assumed some defaults and utilized built-in automation scripts for most of the configuration. Outlined below are all options available: | **Variable Name** | **Default** | **Description** | | ---------------------------------: | :------------------------------------------------- | :----------------------------------------------------------------------- | | `NETWORK_ID` | `costwo` | The network ID to connect to | | `HTTP_HOST` | `0.0.0.0` | HTTP host binding address | | `HTTP_PORT` | `9650` | The listening port for the HTTP host | | `STAKING_PORT` | `9651` | The staking port for bootstrapping nodes | | `PUBLIC_IP` | (empty) | Public facing IP. Must be set if `AUTOCONFIGURE_PUBLIC_IP=0` | | `DB_DIR` | `/app/db` | The database directory location | | `DB_TYPE` | `leveldb` | The database type to be used | | `BOOTSTRAP_IPS` | (empty) | A list of bootstrap server IPs | | `BOOTSTRAP_IDS` | (empty) | A list of bootstrap server IDs | | `CHAIN_CONFIG_DIR` | `/app/conf` | Configuration folder where you should mount your configuration file | | `LOG_DIR` | `/app/logs` | Logging directory | | `LOG_LEVEL` | `info` | Logging verbosity level that is logged into the file | | `AUTOCONFIGURE_PUBLIC_IP` | `0` | Set to 1 to autoconfigure `PUBLIC_IP`, skipped if `PUBLIC_IP` is set | | `AUTOCONFIGURE_BOOTSTRAP` | `0` | Set to 1 to autoconfigure `BOOTSTRAP_IPS` and `BOOTSTRAP_IDS` | | `AUTOCONFIGURE_BOOTSTRAP_ENDPOINT` | `https://coston2-bootstrap.flare.network/ext/info` | Endpoint used for bootstrapping when `AUTOCONFIGURE_BOOTSTRAP=1` | | `AUTOCONFIGURE_FALLBACK_ENDPOINTS` | (empty) | Comma-divided fallback bootstrap endpoints if the primary endpoint fails | | `EXTRA_ARGUMENTS` | (empty) | Extra arguments passed to flare binary |
## Update the node Keep your node up-to-date with the latest stable release of `go-flare` for security and performance improvements. 1. Find the latest stable tag from the go-flare Docker Hub [tags page](https://hub.docker.com/r/flarefoundation/go-flare/tags). 2. Stop and remove the old container: ```bash docker stop flare-node # Or coston2-node, songbird-node, coston-node docker rm flare-node ``` 3. Update the `LATEST_TAG` to the new stable tag and run the same `docker run` command as [previously detailed](/run-node/using-docker#using-docker-cli). Your data in `/mnt/db` (or your chosen volume) will be preserved. 1. Find the latest stable tag from the go-flare Docker Hub [tags page](https://hub.docker.com/r/flarefoundation/go-flare/tags). 2. Stop the old container: ```bash docker compose -f /opt/node/docker-compose.yaml down ``` 3. Update the `docker-compose.yaml` file with the new tag and start the new container: ```bash LATEST_TAG="vX.Y.Z" # <-- Replace with the actual latest tag e.g v1.12.0 yq -i ".services.node.image = flarefoundation/go-flare:${LATEST_TAG}" /opt/node/docker-compose.yaml docker compose -f /opt/node/docker-compose.yaml up -d ``` ## Troubleshooting If you encounter issues, here are some common troubleshooting steps. - **Check Peer Count:** A node needs sufficient peers (typically at least 16) to sync and operate correctly. Use the health endpoint to check your connection count. ```bash # Check the "connectedPeers" value in the output curl -s http://127.0.0.1:9650/ext/health | jq -r '.checks.network.message' ``` - **Monitor Disk Space:** If your node stops syncing, ensure the database location (`/mnt/db`) has enough free space. Run `df -h /mnt/db` to check. - **Restart on Errors:** If the node becomes unhealthy or you see persistent `GetAcceptedFrontier` errors in the logs, a simple restart can often resolve the issue. - Docker CLI: `docker restart ` - Docker Compose: `cd /opt/node && docker compose restart` --- ## Register as Validator This guide explains how to register your Flare node as a validator. On Flare, any node can become a validator by performing a **self-bond** transaction on the P-chain. This transaction links your stake to your node's unique `Node-ID`, activating it as a validator. Validator nodes are crucial for the network's security and perform three primary tasks: - **Validation:** Verify that new transactions are valid. - **Consensus:** Work with other validators on the transactions to be added to the blockchain. - **Block Production:** Bundle transactions into blocks and add them to the blockchain. On Flare, data protocols such as the [Flare Time Series Oracle](/ftso/overview) and [Flare Data Connector](/fdc/overview) are enshrined into the network. To fully participate and earn all potential rewards, validators must also run a [Flare Entity](/run-node/flare-entity) to contribute to these protocols. ## Prerequisites - A machine meeting the [minimum hardware requirements](/run-node/system-requirements). - Minimum self-bond amount of 1M FLR (see [Stake requirements](#stake-requirements)). - A running Flare node, installed [using Docker](/run-node/using-docker) or [from source](/run-node/from-source). Ensure you have copied the staking keys to a persistent directory (last step in the **Run the node** section). ## Secure the node Validator security is paramount, as it directly impacts the integrity and stability of the entire Flare network. A compromised or poorly configured validator poses a risk to the network and your stake. :::danger[Critical Security Measures] Failure to implement these measures significantly increases the risk to your node and potentially the network: - **NEVER** expose the node's main API port (default: `9650`) to the public internet. Use a firewall to **only allow** inbound connections on the staking port (default: `9651/TCP`). - **NEVER** use the same node instance for both validation and serving public RPC API requests. Public APIs expose your node to potential DoS attacks and exploits that could halt your validator. Run a separate node for RPC needs. - **ALWAYS** disable password authentication for SSH. Use strong, key-based authentication exclusively. Consider disabling root login via SSH. ::: ### Recommended hardening - Configure your node to **enable only** the essential APIs required for validation (often just `["web3"]` within `eth-apis`). Explicitly disable admin APIs (`snowman-api-enabled`, `coreth-admin-api-enabled`). - **Firewall Best Practices:** - Implement a stateful firewall with a default-deny policy for inbound traffic. - Consider blocking unnecessary outbound traffic. - Block ICMP traffic (ping, traceroute) unless specifically needed for monitoring within a trusted network. - **Do not** run other network-facing applications (e.g., web servers, databases, other blockchain nodes) on the same operating system instance or IP address as your validator. - **Regularly update** the underlying operating system and the go-flare node software to apply the latest security patches. ## Configure the node As described in [Secure the node](#secure-the-node), a validator node should have minimal APIs enabled. Below is a sample `config.json` demonstrating a secure configuration with limited `eth-apis` and disabled admin APIs: Most of the C-Chain configuration options can be found in the [Avalanche C-Chain Configuration docs](https://build.avax.network/docs/nodes/chain-configs/avalanche-l1s/subnet-evm). Note that the default values are overridden only if specified in the given config file. Only provide values which differ from the defaults to avoid issues with future updates. ```json title="config.json" { "snowman-api-enabled": false, "coreth-admin-api-enabled": false, "coreth-admin-api-dir": "", "eth-apis": ["web3"], "continuous-profiler-dir": "", "continuous-profiler-frequency": 900000000000, "continuous-profiler-max-files": 5, "rpc-gas-cap": 50000000, "rpc-tx-fee-cap": 100, "preimages-enabled": false, "pruning-enabled": true, "snapshot-async": true, "snapshot-verification-enabled": false, "metrics-enabled": true, "metrics-expensive-enabled": false, "local-txs-enabled": false, "api-max-duration": 30000000000, "ws-cpu-refill-rate": 0, "ws-cpu-max-stored": 0, "api-max-blocks-per-request": 30, "allow-unfinalized-queries": false, "allow-unprotected-txs": false, "keystore-directory": "", "keystore-external-signer": "", "keystore-insecure-unlock-allowed": false, "remote-tx-gossip-only-enabled": false, "tx-regossip-frequency": 60000000000, "tx-regossip-max-size": 15, "log-level": "info", "offline-pruning-enabled": false, "offline-pruning-bloom-filter-size": 512, "offline-pruning-data-directory": "" } ``` ## Backup and restore :::danger[Backup your staking keys] It's imperative that prior to staking your node, you backup all three files that identify your node. These files will be generated when the node is started for the first time. - `staker.key` and `staker.crt` - Make up your NodeID - `signer.key` - Defines your node's proof of possession (NodePOP). It generates the BLS key and BLS signature required for staking. If these staking keys are lost due to host migrations or disaster scenarios, you will no longer be able to run the node as it's previous identity. This poses a risk of staked funds being locked against the unrecoverable NodeID until expiry of that stake, resulting in a loss of rewards and negatively impacting the network. ::: To backup the staking keys ensure you complete [Step 3](/run-node/from-source#run-the-node) of copying your staking keys to a persistent directory outside the default location. To backup the staking keys ensure you complete [Step 5](/run-node/using-docker#using-docker-compose) of copying your staking keys to a persistent directory outside the default location. To backup the staking keys ensure you complete [Step 4](/run-node/using-docker#using-docker-cli) of copying your staking keys to a persistent directory outside the default location. ## Run the node Create `staker.key` and `staker.crt` files using ```bash openssl req -x509 -newkey rsa:4096 -keyout staker.key -out staker.crt -days 36500 -nodes -subj '/CN=localhost' ``` You will also need the `signer.key` file which is auto-generated and, by default, found in '~/.avalanchego/staking/' folder. Note that `staker.key` and `staker.crt` are also auto-generated and stored in the same folder, but you must **not** use them for successful validator registration! Now, start your node using command-line flags or environment variables that point to your `staker.key`, `staker.crt` and `signer.key` files. This ensures your node always starts with the correct `Node-ID`. ### From source 1. Add the following flags to your [startup command](/run-node/from-source#run-the-node). For example, if you copied your keys to `/opt/flare/staking/`, your command would look like: ```bash # Assumes staking keys are in the default location ./build/avalanchego --network-id=flare \ --http-host= \ --bootstrap-ips="$(curl -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeIP"}' -H 'content-type:application/json;' https://flare-bootstrap.flare.network/ext/info | jq -r '.result.ip')" \ --bootstrap-ids="$(curl -sX POST --data '{"jsonrpc":"2.0", "id":1, "method":"info.getNodeID"}' -H 'content-type:application/json;' https://flare-bootstrap.flare.network/ext/info | jq -r '.result.nodeID')" \ # highlight-next-line --staking-tls-cert-file="/opt/flare/staking/staker.crt" \ # highlight-next-line --staking-tls-key-file="/opt/flare/staking/staker.key" \ # highlight-next-line --staking-signer-key-file="/opt/flare/staking/signer.key" ``` ### Using Docker CLI 1. Modify your [`docker run`](/run-node/using-docker#using-docker-cli) command to mount the directory containing your keys into the container and set the environment variables to point to the mounted files. For example, if you copied your keys to `/opt/flare/staking/`, your command would look like: ```bash # Find the latest tag at [https://hub.docker.com/r/flarefoundation/go-flare/tags](https://hub.docker.com/r/flarefoundation/go-flare/tags) LATEST_TAG="vX.Y.Z" # e.g., v1.12.0 # Mount the staking volume and set the staking path environment variables docker run -d --name flare-node \ -v /mnt/flare-db:/app/db \ -v /opt/flare/conf:/app/conf \ # highlight-next-line -v /opt/flare/staking:/app/staking \ -v /opt/flare/logs:/app/logs \ # highlight-next-line -p 127.0.0.1:9650:9650 \ -p 0.0.0.0:9651:9651 \ -e NETWORK_ID="flare" \ -e AUTOCONFIGURE_BOOTSTRAP="1" \ -e AUTOCONFIGURE_BOOTSTRAP_ENDPOINT="https://flare-bootstrap.flare.network/ext/info" \ # highlight-next-line -e EXTRA_ARGUMENTS=--staking-tls-cert-file=/app/staking/staker.crt --staking-tls-key-file=/app/staking/staker.key --staking-signer-key-file=/app/staking/signer.key \ flarefoundation/go-flare:${LATEST_TAG} ``` ### Using Docker Compose 1. Modify your [`docker-compose.yaml`](/run-node/using-docker#using-docker-compose) file to include the staking key volume and environment variables. For example, if you copied your keys to `/opt/flare/staking/`, your `docker-compose.yaml` would look like: ```yaml title="/opt/node/docker-compose.yaml" # Mount the staking volume and set the staking path environment variables services: node: image: flarefoundation/go-flare:vX.Y.Z # <-- REPLACE with the latest stable tag container_name: flare-node restart: on-failure ports: # highlight-next-line - "127.0.0.1:9650:9650" - "0.0.0.0:9651:9651" volumes: - /mnt/flare-db:/app/db - /opt/flare/conf:/app/conf # highlight-next-line - /opt/flare/staking:/app/staking - /opt/flare/logs:/app/logs environment: - NETWORK_ID=flare - AUTOCONFIGURE_BOOTSTRAP=1 - AUTOCONFIGURE_BOOTSTRAP_ENDPOINT=https://flare-bootstrap.flare.network/ext/info # highlight-next-line - EXTRA_ARGUMENTS=--staking-tls-cert-file=/app/staking/staker.crt --staking-tls-key-file=/app/staking/staker.key --staking-signer-key-file=/app/staking/signer.key ``` ## Stake and verify With your node running and properly identified, the final step is to submit the self-bond transaction.
Staking phases on Flare. **Summary:** The deployment of validator staking on Flare occurs in three distinct phases, each with its own set of rules and requirements. The following table summarizes the key differences between the phases: | | Launch | Phase 1 | Phase 2\* | Phase 3 | | ------------------------------------------------- | :----: | :-----: | :-------: | :-----: | | Validation open to everybody | ❌ | ✅ | ✅ | ✅ | | Validators must provide own stake | ❌ | ❌ | ✅ | ✅ | | Validators must be data providers to earn rewards | ❌ | ❌ | ✅ | ✅ | | Locked stake can earn staking rewards | ❌ | ❌ | ✅ | ✅ | | Staking rewards are handled onchain | ❌ | ❌ | ❌ | ✅ | | Same rights for staked and wrapped tokens | ❌ | ❌ | ❌ | ✅ | \*Current phase **Detailed breakdown:** :::warning[FlareDrops have ended] FlareDrops concluded on January 30, 2026, completing the 36-month distribution schedule defined under [FIP.01](https://proposals.flare.network/FIP/FIP_1.html). WFLR, rFLR, and staked FLR no longer accrue the FlareDrop reward component. However, **protocol-level rewards remain unchanged**: you continue to earn rewards for FTSO delegation, FLR staking, and FAssets agent participation. Learn more about [FLR's operational utility era](https://flare.network/news/beyond-flaredrops-flr-enters-operational-utility-era). ::: The Flare network is designed to be progressively decentralized, with the transition occurring in three phases: 1. Infrastructure entities will be progressively on-boarded to ensure the network remains operational. 2. Current FTSO data providers must build a minimum stake to function as validators. 3. Existing validators need to enhance their capabilities to become data providers. Each phase will gradually relinquish control, allowing the network to validate independently of the Flare Foundation. **Launch (Jul 2022)** At network launch, 20 validators had their node IDs hard-coded into the client software, preventing other validators from participating. The Flare Foundation managed these nodes and gradually reassigned 16 of them to 4 external entities to increase decentralization. These entities, known as professional validators, are experienced infrastructure providers managing blockchain nodes. During this period, FTSO data providers operated entirely independently of validators. **Phase 1 (July 2023 - Oct 2023)** A network fork enabled Avalanche's proof-of-stake mechanism, opening validation to everyone. Simultaneously, all stakes from the original validators expired. The Flare Foundation loaned all the stakes for the initial validators, maintaining the distribution of validation power while testing proof-of-stake. After some FTSO data providers completed a KYC process, the Flare Foundation loaned them funds to deploy validation nodes and act as validators. Since staking occurs on the P-chain, staked tokens cannot access rewards managed by smart contracts on the C-chain. To address this, a communication mechanism between the two chains is being developed. All staking rewards are manually calculated offchain and then distributed onchain. These calculations will initially be private for fine-tuning and will become public in Phase 2 for verification. **Phase 2 (Current)** Once FTSO data providers have gathered enough stake to ensure the network's continued operation, all stakes loaned by the Flare Foundation to the launch validators will be withdrawn. Professional validators are expected to cease operations unless they provide their own stake. The Flare Foundation might delegate stake to KYC-verified FTSO data providers to help initiate the system. This process, known as stake boosting, will run for a limited time. Staked funds can earn [FlareDrops](https://flare.network/news/flaredrop-guide) and participate in governance but not earn [FTSO](https://flare.network/products/flare-time-series-oracle) delegation rewards. Staking rewards will: - Consider validator uptime and staked amount, which is publicly monitored. - Require that the validator is also an FTSO data provider consistently rewarded for accurate prices. - Be manually calculated offchain using a public script and then distributed onchain. **Phase 3** Once secure communication between the P- and C-chains is established, staking rewards will be managed entirely onchain. The goal is for funds staked on the P-chain to have the same rights as wrapped FLR on the C-chain, enabling them to earn [FTSO](https://flare.network/products/flare-time-series-oracle) rewards, [FlareDrops](https://flare.network/news/flaredrop-guide), and participate in governance.
### Stake requirements | **Requirement** | **Value** | **Description** | | :------------------------------ | :-------- | :-------------------------------------------------------------------------------------------------------------- | | **Minimum self-bond amount** | 1M FLR | The minimum stake required to register a validator. | | **Minimum delegation amount** | 50K FLR | The minimum amount an FLR holder can delegate to a validator. | | **Minimum stake duration** | 2 months | The minimum time a validator's self-bond must be locked. | | **Minimum delegation duration** | 2 weeks | The minimum time delegated funds must be locked. | | **Stake delay** | Immediate | The time between submitting a stake and it becoming active. | | **Delegation factor** | 15x | A multiplier on the self-bond that sets the maximum total stake (including delegations) a validator can accept. | | **Maximum total stake** | 200M FLR | The absolute maximum stake a validator can have, including all delegations. | | **Max validators per entity** | 4 | The maximum number of validators a single entity can operate. | See [FIP.05](https://proposals.flare.network/FIP/FIP_5.html) for further details. ### Perform the self-bond :::warning[Self bond & expiry are locked] Once you set the self-bond and its expiry, both are locked. You can't add to or remove the self-bond until the expiry has passed - choose values carefully. ::: To perform the actual staking transaction (the self-bond), you will use the Flare Stake Tool. To retrieve your `Node-ID`, you need to query your running node: ```bash curl --data '{ "jsonrpc":"2.0", "method":"info.getNodeID", "id":1, "params":{} }' -H 'content-type:application/json;' http://localhost:9650/ext/info | jq -r ".result.nodeID" ``` Follow the instructions in the guide below to stake FLR to your `Node-ID`. ### Verify validator status :::tip[Get listed on explorers] Submit a PR to [TowoLabs/ftso-signal-providers](https://github.com/TowoLabs/ftso-signal-providers) adding your validator name, description, delegation address, and logo (per repo guidelines). Once merged, explorers should update on their next sync. ::: Once your self-bond transaction is confirmed and the staking period begins, your node will join the active validator set. You can monitor its status using an API call or a validator monitoring site. - **Via API:** ```bash NODE_ID="" # <-- Replace with your actual Node-ID curl --data '{ "jsonrpc": "2.0", "method": "platform.getCurrentValidators", "params": { "nodeIDs": ["'"${NODE_ID}"'"] }, "id": 1 }' -H 'content-type:application/json;' https://flare-api.flare.network/ext/P | jq ``` Check the following fields in the response: - `uptime`: Percentage of time the queried node has reported the peer as online and validating. - `connected`: If the node is connected and tracks the network. See the full P-chain API details in the [Avalanche Documentation](https://build.avax.network/docs/rpcs/p-chain). - **Via Web:** Use the [Flare Validator Tracker](https://flare-validators.flare.network/) to see your validator's status. --- ## Set Up Flare Entity This guide will walk you through setting up a Flare Entity, consisting of a validator node and a data provider system (FSP) for the Flare network. The [FSP](/network/fsp) consists of [Flare Time Series Oracle (FTSO)](https://dev.flare.network/ftso/overview.md) and [Flare Data Connector (FDC)](https://dev.flare.network/fdc/overview.md) sub-protocols. A Flare Entity consists of the following six components: 1. **Flare System Client**: Manages interactions with FTSO smart contracts, including data collection, submission, voter registration, and system tasks. 2. **C-chain Indexer**: Tracks FSP-related blockchain transactions and events, enabling data calculations and action triggers. 3. **FTSO Client**: Provides anchor feed submissions and median data to the System Client. 4. **Fast Updates Client**: Submits block-latency feeds to Fast Updates contracts. 5. **Feed Value Provider**: Retrieves data from exchanges and supplies current feed values (prices). 6. **FDC Client**: Provides FDC protocol voting round data to the System Client. :::warning[Rewards and Minimal conditions] After the introduction of [minimal conditions](/network/fsp/rewarding#minimal-conditions) in [FIP.10](https://proposals.flare.network/FIP/FIP_10.html), a Flare Entity needs to be both **available** and **performant** across all Flare protocols in order to be eligible for rewards. Learn how to check if your Flare Entity meets minimal conditions and the rewards it accrued in the [FAQs](#faqs) section (see **FAQ F1** and **FAQ F2**). ::: ## Prerequisites - A machine meeting the [minimum hardware requirements](/run-node/system-requirements). - A registered [validator node](/run-node/register-validator). - [Docker Engine](https://docs.docker.com/engine/install/), ensure the Docker data root directory has sufficient disk space. If necessary, configure Docker to use a specific storage location or mount point (refer to Docker's [storage documentation](https://docs.docker.com/engine/storage/)). - [jq](https://jqlang.org) - [envsubst](https://www.gnu.org/software/gettext/manual/html_node/envsubst-Invocation.html) - [yarn](https://yarnpkg.com) (optional, for performing [automated registration](#register-and-fund-fsp-addresses) on testnets) :::tip To avoid using `sudo` each time you run the `docker` command, add your user to the Docker group after installation: ```bash sudo usermod -a -G docker $USER ``` Log out and log back in or restart your system for the changes to take effect. ::: ## Registration Before deploying the Flare Entity components, several one-time registration steps must be completed onchain. This involves registering FSP operational addresses, funding them, registering a sortition key for Fast Updates, and linking your validator node ID to your entity's identity. ### Register and fund FSP addresses You need five specific addresses for FSP operations. You can register them using an automated script (Testnets only) or manually via contract calls (Mainnet).
Method 1: Automated Registration (Testnets only). :::danger This automated method requires placing private keys in a JSON file. This significantly increases security risk and is **NOT recommended for mainnet**. Use this method **only** where key security is less critical. For mainnet, **always use Method 2: Manual process.** ::: On testnets, you can automate the registration of your five FSP addresses **and** your sortition public key using scripts from the `flare-smart-contracts-v2` repository. 1. Clone and build [flare-smart-contracts-v2](https://github.com/flare-foundation/flare-smart-contracts-v2/): ```bash git clone https://github.com/flare-foundation/flare-smart-contracts-v2/ cd flare-smart-contracts-v2 yarn yarn c ``` 2. Create an `entities.json` file with the following account addresses and private keys. Generate a sortition private key by following step 1 in [Register sortition key](#register-sortition-key): ```json [ { "identity": { "address": "
", "privateKey": "" }, "submit": { "address": "
", "privateKey": "" }, "submitSignatures": { "address": "
", "privateKey": "" }, "signingPolicy": { "address": "
", "privateKey": "" }, "delegation": { "address": "
", "privateKey": "" }, "sortitionPrivateKey": "" } ] ``` 3. Rename `.env.template` to `.env` and add the path to your `entities.json` file: ```text title=".env" ENTITIES_FILE_PATH=entities.json # <-- REPLACE WITH PATH TO YOUR entities.json ``` 4. Run the registration tasks ([`register-entities.ts`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/deployment/tasks/register-entities.ts) and [`register-public-keys.ts`](https://github.com/flare-foundation/flare-smart-contracts-v2/blob/main/deployment/tasks/register-public-keys.ts)): ```bash yarn run register_entities_network_coston2 yarn run register_public_keys_network_coston2 ``` ```bash yarn run register_entities_network_coston yarn run register_public_keys_network_coston ``` If these scripts complete successfully, you have registered both the FSP addresses and the sortition key. You can proceed directly to [Fund FSP addresses](#fund-fsp-addresses).
#### Method 2: Manual registration On mainnet, or if you prefer not to use the automated script on testnets, you must manually register the required FSP addresses by interacting directly with the [`EntityManager`](/network/fsp/solidity-reference/IEntityManager) contract. 1. Ensure you have generated the following five addresses and **securely stored their private keys**. Understand their roles: - `Identity`: The central identity address. **Crucially, keep the private key secure (e.g., cold storage on mainnet).** This address initiates proposals and signs registrations. - `Submit`: Used for sending FTSO commit/reveal transactions. - `SubmitSignatures`: Used for sending FTSO result signatures (often separate from `Submit` to manage transaction nonces). - `SigningPolicy`: Used for signing FTSO data during voting and for reward epoch configurations. - `Delegation`: Public address for receiving WNat (e.g., WFLR, WC2FLR) delegations to increase vote power and collect rewards. 2. For each address role (`Submit`, `SubmitSignatures`, `SigningPolicy`, `Delegation`), perform the two-step propose-confirm process using the `EntityManager` contract (see [FSP Reference](/network/fsp/solidity-reference)): - **Step A (Propose):** Call the relevant `proposeAddress` method from your `Identity` address. Provide the address you want to register for that role as the argument. - **Step B (Confirm):** Call the corresponding `confirmAddressRegistration` method from the address you are actually registering (e.g., call `confirmSubmitAddressRegistration` using the `Submit` address's private key). | Role to Register | Step A: Method Called by `Identity` Address | Step B: Method Called by Proposed Address | | :----------------- | :---------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | `Submit` | [`proposeSubmitAddress`](/network/fsp/solidity-reference/IEntityManager#proposesubmitaddress) | [`confirmSubmitAddressRegistration`](/network/fsp/solidity-reference/IEntityManager#confirmsubmitaddressregistration) | | `SubmitSignatures` | [`proposeSubmitSignaturesAddress`](/network/fsp/solidity-reference/IEntityManager#proposesubmitsignaturesaddress) | [`confirmSubmitSignaturesAddressRegistration`](/network/fsp/solidity-reference/IEntityManager#confirmsubmitsignaturesaddressregistration) | | `SigningPolicy` | [`proposeSigningPolicyAddress`](/network/fsp/solidity-reference/IEntityManager#proposesigningpolicyaddress) | [`confirmSigningPolicyAddressRegistration`](/network/fsp/solidity-reference/IEntityManager#confirmsigningpolicyaddressregistration) | | `Delegation` | [`proposeDelegationAddress`](/network/fsp/solidity-reference/IEntityManager#proposedelegationaddress) | [`confirmDelegationAddressRegistration`](/network/fsp/solidity-reference/IEntityManager#confirmdelegationaddressregistration) | #### Fund FSP addresses All registered FSP addresses (`Identity`, `Submit`, `SubmitSignatures`, `SigningPolicy`, `Delegation`) require funding with the network's native token (FLR, SGB, etc.) to cover transaction gas fees. Additionally, the `Delegation` address needs sufficient WNat balance to achieve voting power in the FTSO protocols. You can wrap native tokens directly to this address or receive delegations from the community. - Wrapping Portals: - [Development Portal](https://portal.flare.rocks) (Flare Testnet Coston2, Songbird Testnet Coston) - [Flare Portal](https://portal.flare.network) (Flare Mainnet, Songbird Canary-Network) - The `Delegation` address needs at least **150 WNat** (e.g., WFLR, WC2FLR) for its vote weight to be recognized by the protocols (due to weight normalization). ### Register sortition key This key pair is used for the FTSOv2 Fast Updates protocol. 1. Generate a sortition key pair using the [fast-updates/go-client](https://github.com/flare-foundation/fast-updates/pkgs/container/fast-updates%2Fgo-client) image: ```bash docker run --rm ghcr.io/flare-foundation/fast-updates/go-client:latest keygen ``` This will generate an output of the form: ```json { "PublicKeyX": "0x14ce9f57394f6dcdba82816808deb5f9c0fd9e5621edeeef35ff1e55346c4ccf", "PublicKeyY": "0x131b26a181c5a1f1281f7bd8c745dabb44c5986bb408f88052b336390fcaec51", "PrivateKey": "0x0f795088442f405031f54fb6e2f19ea05dc7769755cc28a947df99f6c4b5af22" } ``` 2. Register public key: - If you used the **Automated Registration** script on a testnet (Method 1 above), this step is already completed, skip to [Register validator node](#register-validator-node). - If you used **Manual Registration** (Method 2) or need to register manually: 1. Create a signature proving your `Identity` address owns the generated sortition private key. Use the `keygen` command again, providing the `PrivateKey` (from Step 1) and your `Identity` address. ```bash # Replace placeholders with your actual values docker run --rm ghcr.io/flare-foundation/fast-updates/go-client:latest keygen \ --key PrivateKey \ # <-- REPLACE WITH YOUR GENERATED PrivateKey --address Identity # <-- REPLACE WITH YOUR Identity ADDRESS ``` This will generate a signature of the form: ```plaintext Signature generated: 0x10b0f6b68acf899944ce9406c807f38e904401dcf962d7fdd9943b0dee0222321f6863ed9f46d048695942793f1c16158b9a886b6ff2120390f5ddcbbdb0d3b02d8fff2c8f0583c8269650f97a169f5c5ea04ce3bdd91ca959188ac2cf2ff517 ``` 2. Call the [`registerPublicKey`](/network/fsp/solidity-reference/IEntityManager#registerpublickey) method from your `Identity` address. Provide the `PublicKeyX`, `PublicKeyY` (from Step 1), and the `Signature` (from Step 2a) as arguments. ```solidity registerPublicKey( bytes32 _part1, // <-- REPLACE WITH YOUR GENERATED PublicKeyX bytes32 _part2, // <-- REPLACE WITH YOUR GENERATED PublicKeyY bytes _verificationData // <-- REPLACE WITH YOUR GENERATED Signature ) ``` ### Register validator node Link your registered validator node's ID to your Flare Entity `Identity` address via the [`EntityManager`](/network/fsp/solidity-reference/IEntityManager) contract. 1. Find your active validator node's `staker.key` and `staker.crt` files (Default paths: `~/.avalanchego/staking/`). 2. Prepare shell variables for the file paths and your `Identity` address (without the `0x` prefix). ```bash PATH_TO_CRT=~/.avalanchego/staking/staker.crt ZERO_PREFIX=0000000000000000000000000000000000000000000000000000000000000000 PATH_TO_KEY=~/.avalanchego/staking/staker.key IDENTITY_ADDRESS=ab...123 # <-- REPLACE WITH YOUR Identity ADDRESS WITHOUT 0x prefix ``` 3. Use the following commands to generate the required contract arguments: `_nodeId` (NodeID derived from certificate): ```bash cat $PATH_TO_CRT | tail -n +2 | head -n -1 | base64 -d | openssl dgst -sha256 -binary | openssl rmd160 -provider legacy -binary | xxd -p | sed -e 's/^/0x/;' ``` `_certificateRaw` (Raw certificate bytes): ```bash cat $PATH_TO_CRT | tail -n +2 | head -n -1 | base64 -d | xxd -p | tr -d '\n' | sed -e 's/^/0x/;' && echo ``` `_signature` (Signature proving Identity owns the validator key): ```bash echo -n $ZERO_PREFIX$IDENTITY_ADDRESS | xxd -r -p | openssl dgst -sha256 -sign $PATH_TO_KEY | xxd -p | tr -d '\n' | sed -e 's/^/0x/;' && echo ``` 4. Call the [`registerNodeId`](/network/fsp/solidity-reference/IEntityManager#registernodeid) method on `EntityManager` using your Identity address. Provide the hex values generated in Step 3 as arguments. ```solidity registerNodeId( bytes20 _nodeId, // <-- REPLACE WITH YOUR GENERATED _nodeId bytes _certificateRaw, // <-- REPLACE WITH YOUR GENERATED _certificateRaw bytes _signature // <-- REPLACE WITH YOUR GENERATED _signature ) ``` ## Setup FDC With the necessary registrations complete, this section guides you through setting up the Flare Data Connector (FDC) components. The goal is to run the underlying blockchain nodes (or connect to existing ones) and the associated FDC indexers and verifiers. These components securely retrieve and verify data from various blockchains for the Flare network. The FDC handles data differently depending on the source chain type: - **UTXO-Based Chains** (e.g., Bitcoin, Dogecoin): An indexer service syncs with the blockchain, storing relevant data locally. A Verifier API server then exposes this indexed data. - **EVM Chains** (e.g., Ethereum, Flare, Songbird): The Verifier API server queries an RPC node directly for necessary data. :::warning[Resource Intensive] Deploying the full FDC suite, including multiple blockchain full nodes (BTC, DOGE, XRP, ETH, plus Flare/Songbird for EVM verification) and indexers, demands significant hardware resources. Ensure your system meets these combined requirements (see main [System Requirements](/run-node/system-requirements) and factor in this additional load). ::: ### Required components The [fdc-suite-deployment](https://github.com/flare-foundation/fdc-suite-deployment) repository provides configurations for deploying the following components: #### Blockchain node images | Network | Blockchain node image | | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Bitcoin | [flarefoundation/bitcoin](https://hub.docker.com/r/flarefoundation/bitcoin) | | Dogecoin | [flarefoundation/dogecoin](https://hub.docker.com/r/flarefoundation/dogecoin) | | Ripple | [flarefoundation/rippled](https://hub.docker.com/r/flarefoundation/rippled) | | Ethereum | [ethereum/client-go](https://hub.docker.com/r/ethereum/client-go) & [prysm](https://prysm.offchainlabs.com/docs/install-prysm/install-with-docker/) | #### Indexers and verifiers | Network | Indexer | Verifier | | ------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | BTC | [flare-foundation/verifier-utxo-indexer](https://github.com/flare-foundation/verifier-utxo-indexer) | [flare-foundation/verifier-indexer-api](https://github.com/flare-foundation/verifier-indexer-api) | | DOGE | [flare-foundation/verifier-utxo-indexer](https://github.com/flare-foundation/verifier-utxo-indexer) | [flare-foundation/verifier-indexer-api](https://github.com/flare-foundation/verifier-indexer-api) | | XRP | [flare-foundation/verifier-xrp-indexer](https://github.com/flare-foundation/verifier-xrp-indexer) | [flare-foundation/verifier-indexer-api](https://github.com/flare-foundation/verifier-indexer-api) | | EVM | - | [flare-foundation/evm-verifier](https://github.com/flare-foundation/evm-verifier) |
EVM verification, using existing nodes and dependencies. - **EVM Verification:** Note that Flare Mainnet and Songbird Canary-Network nodes (see [Run Node guides](/run-node/)) are also implicitly required for EVM verification tasks run by the FDC Client later, but are typically run as part of your core Flare Entity / Validator setup, not within the `fdc-suite-deployment`. - **Using Existing Nodes:** You are not required to deploy all listed blockchain node images using this repository. If you operate your own compatible RPC nodes (e.g., for Bitcoin), you can **configure the corresponding Verifier/Indexer `.env` file (detailed below) with your node's RPC URL and credentials** instead of starting the node image provided here. - **Splitting Deployment:** While possible to run components across multiple servers, this guide focuses on the single-machine setup provided by the repository, which may be simpler to manage. - **Dependencies:** Indexers and Verifiers require access to a synced and operational blockchain node for their respective chain. Ensure node availability before starting dependent services.
### Configure blockchain nodes 1. Start by cloning the [flare-foundation/fdc-suite-deployment](https://github.com/flare-foundation/fdc-suite-deployment) repo: ```bash git clone https://github.com/flare-foundation/fdc-suite-deployment.git cd fdc-suite-deployment ``` 2. (optional) Images are typically pulled automatically from the GitHub Container Registry. If manual building is needed: ```bash # Clone the specific component repo first, then build # docker build -t . # replace image tag with tag used in docker-compose.yaml ``` 3. To configure node authentication, navigate into the specific node's directory (e.g., `nodes-mainnet/btc`) within the cloned repo and generate credentials where required. **Keep generated credentials secure** as they will be needed for the Verifier configuration. - **Bitcoin (BTC) / Dogecoin (DOGE):** ```bash # Example for BTC cd nodes-mainnet/btc ./generate-password.sh # Follow prompts or use ./rpcauth.py # Note down the generated username/password/rpcauth string for later use cd ../.. # Return to fdc-suite-deployment root ``` _(Repeat for `nodes-mainnet/doge` if needed)_ - **Ripple (XRP):** No specific authentication setup needed via this repo's config. - **Ethereum (ETH):** Generate JWT secret for communication between Execution (Geth) and Consensus (Prysm) clients: ```bash # Ensure you are in the fdc-suite-deployment root directory openssl rand -hex 32 > nodes-mainnet/eth/jwt.hex ``` :::warning[Exposing ports] The default `docker-compose.yaml` files provided in `fdc-suite-deployment` for blockchain nodes might expose standard RPC ports. Review the `ports` section in the relevant `docker-compose.yaml` files and adjust firewall rules on your host machine accordingly to restrict access based on your security policy. ::: ### Configure indexers and verifiers Configuration for the indexer and verifier services is managed centrally via the `.env` file in the root of the `fdc-suite-deployment` repo. 1. Create and Edit `.env`: ```bash cp .env.example .env ``` Now, carefully edit the `.env` file, providing values for all variables relevant to the FDC components you are deploying. Pay close attention to: - **RPC Node Connection Details (`*_URL`, `*_RPC_USERNAME`, `*_RPC_PASSWORD` / `*_RPC_AUTH`)**: Provide the full URL and credentials for connecting to each required blockchain node. - If using nodes deployed via `fdc-suite-deployment` on the _same host_, you can often use `172.17.0.1` (Docker's default bridge IP) as the hostname in the URL (e.g., `http://admin:@172.17.0.1:8332/` for BTC, using the password generated earlier). - If using _external_ nodes (your own or third-party), use their publicly accessible RPC endpoint URL and associated credentials. - **Start Block Number (`*_START_BLOCK_NUMBER`)**: Crucial for the first run only. Set this for each UTXO indexer (BTC, DOGE, XRP) to a block number finalized approximately **14 days prior** to the current date. (Tip: Use a blockchain explorer for the respective chain to find a block number from around that time). This limits initial indexing duration. - **Testnet Mode (`TESTNET`):** Set to `true` if deploying for Coston/Coston2, otherwise `false` (or omit). - **Verifier API Keys (`VERIFIER_API_KEYS`)**: Set one or more comma-separated **secret keys**. These keys grant access to your verifier API servers and will be needed by the FDC Client (in the main Flare Entity deployment). Generate strong, unique keys. - **Database Passwords (`*_DB_PASSWORD`)**: Set secure, random passwords for the internal indexer databases. 2. After saving your `.env` file, run the provided script. This injects your `.env` values into the configuration templates used by the individual services. ```bash ./generate-config.sh ``` _This populates files in `verifiers/`, `evm-verifier/`, etc. Remember to **re-run this script** every time you make changes to your `.env` file._ ### Start FDC Services :::warning[Order of starting services] The order of starting the services is crucial. Ensure that the blockchain nodes are fully operational and ideally synced before starting their corresponding indexers and verifiers. ::: #### Start blockchain nodes 1. Go to the directory for the specific blockchain node, e.g., `nodes-mainnet/btc`. 2. Start the node: ```bash docker compose up -d ``` _Repeat for each blockchain node image you are deploying._ #### Start indexers and verifiers 1. Go to the appropriate verifier directory, e.g., `verifiers/btc`. 2. Start the verifier: ```bash docker compose up -d ``` _Repeat for all verifiers corresponding to the chains you need._ :::tip[Verify FDC Services & Initial Indexing] After starting the FDC nodes, indexers, and verifiers, check their status using `docker ps`. Review initial logs via `docker compose logs ` (e.g., `docker compose logs btc-indexer` in the `verifiers/btc` directory) to confirm startup without immediate errors. ::: ## Setup FTSO To participate in the Flare Time Series Oracle (FTSO), your Flare Entity needs access to real-time price data for various assets. This is provided by a **Feed Value Provider** service that you must run. It fetches price data from your chosen external sources (e.g., multiple exchanges) and serves it via a specific API endpoint to your Flare Entity's FTSO clients (Fast Updates Client and FTSO Client). :::warning[Implement Your Own Production Provider] The example implementation referenced below ([`flare-foundation/ftso-v2-example-value-provider`](https://github.com/flare-foundation/ftso-v2-example-value-provider)) retrieves data from public sources but is intended ONLY for testing, demonstration, or initial integration purposes. For reliable mainnet operation and reward eligibility, you MUST implement, deploy, and maintain your own robust Feed Value Provider. This involves: - Sourcing data from diverse, reliable, low-latency upstream providers (exchanges, APIs). - Implementing logic for data cleaning, validation, and aggregation. - Ensuring high availability and performance of your service. Failure to provide accurate, timely, and robust data through your provider will negatively impact your FTSO performance and rewards. ::: ### Running a custom provider - Develop your service adhering strictly to the [Required Endpoints Specification](#required-endpoints-specification) below. - Deploy your custom provider service using production-grade practices (e.g., as a container with restart policies, behind a load balancer if needed, with monitoring). - The provider's API endpoint must be network-accessible from the environment where your [main Flare Entity services run](#run-flare-entity) (i.e., where the `flare-systems-deployment` Docker containers will run). It typically _does not_ need public internet exposure. Note down its internal network URL (e.g., `http://:`) for the `VALUE_PROVIDER_URL` variable in the final deployment configuration. #### Required endpoints specification Your custom Feed Value Provider must implement the following two HTTP `POST` endpoints. Both endpoints expect and return JSON payloads (`Content-Type: application/json`).
**`POST /feed-values`** - **Description**: Returns the feed values without specifying the voting round, used by FTSOv2 Fast Updates client. For category and name values, refer to [FTSOv2 block-latency feeds](/ftso/feeds). - **Example request:** ```bash curl -X 'POST' 'http://localhost:3101/feed-values/' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{"feeds": [{ "category": 1, "name" : "BTC/USD" }]}' ``` - **Example response:** ```json { "data": [ { "feed": { "category": 1, "name": "BTC/USD" }, "value": 71285.74004472858 } ] } ```
**`POST /feed-values/:votingRoundId`** - **Description**: Returns the feed values for the specified voting round, used by FTSOv2 Scaling client. For category and name values, refer to [FTSOv2 anchor feeds](/ftso/scaling/anchor-feeds). - **Example request:** ```bash curl -X 'POST' 'http://localhost:3101/feed-values/0' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{"feeds": [{ "category": 1, "name" : "BTC/USD" }]}' ``` - **Example response:** ```json { "votingRoundId": 0, "data": [ { "feed": { "category": 1, "name": "BTC/USD" }, "value": 71287.34508311428 } ] } ```
### Using the example provider For testing and development, you can run the [flare-foundation/ftso-v2-example-value-provider](https://github.com/flare-foundation/ftso-v2-example-value-provider): ```bash # Exposes the example provider API on port 3101 of the host machine docker run --rm -it --publish "0.0.0.0:3101:3101" ghcr.io/flare-foundation/ftso-v2-example-value-provider ``` If running Docker locally, the API documentation is typically available at [`http://localhost:3101/api-doc`](http://localhost:3101/api-doc).
Troubleshooting without live data. To test integration without live data, run the example provider in fixed mode: ```bash docker run --rm -it --env VALUE_PROVIDER_IMPL=fixed --publish "0.0.0.0:3101:3101" ghcr.io/flare-foundation/ftso-v2-example-value-provider ``` Check container logs for `WARN [FixedFeed] Initializing FixedFeed....`
Remove specific exchanges from the example value provider. If you want to remove specific exchanges from the example value provider, you can do so by modifying `src/config/feeds.json`: Example removing Binance and Bybit exchanges with `jq`: ```bash curl -fs -o feeds.json https://raw.githubusercontent.com/flare-foundation/ftso-v2-example-value-provider/refs/heads/main/src/config/feeds.json && \ cp feeds.json feeds.json.bak && \ jq 'map(.sources |= map(select(.exchange | ascii_downcase != "binance" and ascii_downcase != "bybit")))' feeds.json > feeds.clean && \ mv feeds.clean feeds.json ```
### Create Fast Updates accounts The FTSOv2 Fast Updates protocol uses dedicated accounts for submitting frequent price updates, separate from the main FSP addresses to manage transaction nonces effectively. - Generate at least three (3) new, unique blockchain accounts. These accounts must **not** be any of the five main [FSP addresses](#register-and-fund-fsp-addresses) (`Identity`, `Submit`, etc.). - Securely store the private keys for these newly created accounts. You will need them later for the `FAST_UPDATES_ACCOUNTS` variable in the final deployment `.env` file. - Ensure each of these new Fast Updates accounts is funded with a sufficient amount of the network's native token (e.g., FLR, SGB) to cover the gas costs of frequent transaction submissions. Monitor balances periodically. ## Run Flare Entity With prerequisites met, registrations complete, and FDC/FTSO components configured, you can now deploy and run the main Flare Entity system: 1. Clone [flare-systems-deployment](https://github.com/flare-foundation/flare-systems-deployment) repository: ```bash git clone https://github.com/flare-foundation/flare-systems-deployment.git cd flare-systems-deployment ``` 2. Copy the example environment file: ```bash cp .env.example .env ``` 3. Carefully edit the `.env` file, providing the correct values gathered during the previous setup stages. While reviewing `env.example` for _all_ possible settings is recommended (defaults may not suit all environments), ensure these key variables are correctly set based on your Registration, FDC, and FTSO setup: - **RPC** - `NODE_RPC_URL`: Your RPC Node or a [private RPC provider](/network/developer-tools#rpcs). - `NODE_API_KEY` (optional): If your RPC provider requires it. - **Identity & Keys:** - `IDENTITY`: Public `Identity` address from [FSP addresses](#register-and-fund-fsp-addresses). - `SUBMIT_PK`, `SIGNATURES_PK`, `SIGNING_PK`: Private keys from [FSP addresses](#register-and-fund-fsp-addresses). - **FTSO:** - `VALUE_PROVIDER_URL`: URL of the feed value provider in [Setup FTSO](#setup-ftso). - `FAST_UPDATES_ACCOUNTS`: Private keys of the [Fast Updates accounts](#create-fast-updates-accounts). - `FAST_UPDATES_SORTITION_PRIVATE_KEY`: Private key of the [sortition key](#register-sortition-key). - **FDC:** - `__URL`: URLs for each FDC [verifier services](#start-indexers-and-verifiers) you deployed, e.g. `BTC_PAYMENT_URL=http://:port` - `__API_KEY` (optional): API keys configured for your FDC verifiers, if any. :::danger[Secure Your `.env` File] This file contains **highly sensitive** private keys and API keys. Compromise of these keys can lead to loss of funds, unauthorized actions, and a compromised entity. - Set strict file permissions (e.g., `chmod 600 .env`) so only the owner can read/write. - Ensure it's **never** committed to Git or any version control system. - Limit access to the server and user account running the deployment. ::: 4. Update your `.env` to include all services: ```plaintext COMPOSE_PROFILES=fsp,fast-updates ``` 5. (Optional) By default, the `c-chain-indexer` requires 10 million blocks (≈20 days) of node history. - To shorten this window, edit [`template-configs/c-chain-indexer.template.toml`](https://github.com/flare-foundation/flare-systems-deployment/blob/main/template-configs/c-chain-indexer.template.toml) to a value closer to the head of your node. - **Do not** go below 350,000 blocks (≈7 days / 2 reward epochs), or the FTSO client may fail. 6. Run the helper script to apply your `.env` settings to each Docker service: ```bash ./populate_config.sh ``` _Remember to **re-run this script** every time you make changes to your `.env` file._ 7. Launch the Flare Entity stack in detached mode: ```bash docker compose up -d ``` _Note: The first startup will pull all required images. This may take several minutes depending on your network speed._ ### Verify deployment success Once your services are up, follow these steps to confirm everything is running smoothly: 1. Check running containers: ```bash docker ps ``` Ensure these key containers appear in the list and have `Up` status: - `system-client` - `c-chain-indexer` - `fast-updates-client` (if enabled) - `ftso-client` - `fdc-client` 2. For each critical service, open a live log stream in a separate terminal: ```bash docker compose logs -f ``` Replace `` with the actual service name (e.g., `system-client`, `c-chain-indexer`, etc.). If your Flare Entity is working correctly, congratulations! Refer to the [Troubleshooting](#troubleshooting) and [FAQs](#faqs) sections for common issues and ongoing checks. ## Update Flare Entity Keep your Flare Entity software up-to-date with the latest official releases provided through the Docker images. 1. (Optional) For a potentially cleaner update, especially if major changes occurred, you can stop the running containers: ```bash docker compose down ``` 2. Fetch the newest images specified in the project's `docker-compose.yaml` file: ```bash docker compose pull ``` 3. (If Required) If the new software version requires changes to configuration, carefully modify your `.env` file according to the release notes, and **then re-run the configuration generation script**: ```bash # Modify .env if necessary ./populate_config.sh ``` 4. Start the services using the updated images (and configuration, if changed). `docker compose up -d` will typically recreate containers whose images have changed. ```bash docker compose up -d ``` 5. After restarting, re-check container status (`docker ps`) and monitor logs as described in the [Verify deployment success](#verify-deployment-success) above to ensure all services start correctly with the new versions. ## Troubleshooting :::tip[Get listed on explorers] Submit a PR to [TowoLabs/ftso-signal-providers](https://github.com/TowoLabs/ftso-signal-providers) adding your validator name, description, delegation address, and logo (per repo guidelines). Once merged, explorers should update on their next sync. :::
T1. `system-client` is not doing anything Likely, your entity is not registered as a voter for the current reward epoch. There is a time window for voter registration on every reward epoch, and if you leave things running you should eventually see `RegisterVoter success` in the logs. It should then start submitting data successfully in the **following** reward epoch.
T2. `system-client` fails to fetch sub-protocol data The `c-chain-indexer` may still be indexing data, once it's finished you should see `Indexer at block x` in the logs. If it's still processing blocks you will see various errors across all services.
T3. `fast-updates` client is not doing anything If you have low weight it may take some time until you are selected to submit an update.
T4. Insufficient funds errors on `fast-updates` client If your Fast Updates client is showing the following error, it means that the account you are using to submit transactions does not have enough funds to cover the gas fees. ```plaintext [04-28|09:49:30.890] ERROR client/transaction_queue.go:139 Error executing transaction: insufficient funds for gas * price + value: address 0x57...7B6d have (0) want (187500000000000000) ``` You need to fund [all Fast Updates accounts](#create-fast-updates-accounts) account with enough native tokens to cover the gas fees.
T5. Icon and logo don't show up on Flare explorers Submit a PR to [TowoLabs/ftso-signal-providers](https://github.com/TowoLabs/ftso-signal-providers) adding your validator name, description, delegation address, and logo (per repo guidelines). Once merged, explorers should update on their next sync.
## FAQs
F1. How do I check if my Flare Entity meets Minimal Conditions? Meeting [minimal conditions](/network/fsp/rewarding#minimal-conditions) for availability and performance in each reward epoch is necessary for FSP reward eligibility. You can check your entity's status using two primary methods: **Method 1: Flare Systems Explorer (Web Interface)** This is the easiest and most reliable way to check your status. 1. Navigate to the **Minimal Conditions** tab on the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/providers?tab=minimalConditions). 2. Find your entity by searching for your `Identity` address. 3. Review the status column for the relevant reward epoch(s). A **`YES`** indicates conditions were met for that epoch. **Method 2: Explorer Backend API (CLI)** You can query the API that powers the explorer for programmatic access. 1. You'll need your entity's registered `Identity` address, without the `0x` prefix. ```bash IDENTITY_ADDRESS="563...1a8" # <-- REPLACE WITH YOUR Identity ADDRESS WITHOUT 0x ``` 2. Query the API and extract the minimal conditions status: ```bash curl "https://flare-systems-explorer-backend.flare.network/api/v0/entity?query=${IDENTITY_ADDRESS}" \ -H "Accept: application/json" | jq -r '.results[0].entityminimalconditions' ``` 3. An example output might look like: ```json { "id": 184955, "reward_epoch": 289, // Epoch the status applies to "ftso_scaling": true, // Meets minimal condition for FTSO Scaling "ftso_fast_updates": true, // Meets minimal condition for FTSO Fast Updates "staking": true, // Meets minimal condition for staking "fdc": true, // Meets minimal condition for FDC "passes_held": 3, "strikes": 0, "new_number_of_passes": 3, "eligible_for_reward": true, // All minimal conditions met => eligible for rewards "entity": 42 } ```
F2. Where can I find calculated rewards data and my specific rewards? Reward amounts for both staking and FSP participation are calculated and published periodically. Always refer to official Flare sources for the latest data locations. **Validator Staking Rewards:** Calculated every 4 reward epochs (every 14 days, starting Mon/Thu). Check the `validator-rewards` directory within the **[flare-foundation/reward-scripts](https://github.com/flare-foundation/reward-scripts/tree/main/generated-files/validator-rewards)** repository on GitHub. To inspect your entity's validator rewards for a specific epoch via CLI: 1. Get your `NODE_ID` and the `EPOCH_ID` from the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/providers). ```bash NODE_ID="NodeID-CoN...RS" # <-- REPLACE WITH YOUR Node ID EPOCH_ID="288" # <-- REPLACE WITH THE REWARD EPOCH YOU WANT TO CHECK ``` 2. Use the following command to fetch and display your validator rewards: ```bash curl -s "https://raw.githubusercontent.com/flare-foundation/reward-scripts/refs/heads/main/generated-files/reward-epoch-${EPOCH_ID}/nodes-data.json" | \ jq --arg node_id "${NODE_ID}" \ '.[] | select(.nodeId == $node_id) | (.validatorRewardAmount | tonumber / 1000000000000000000) | "Validator Rewards: \((. * 100 | floor) / 100) FLR"' ``` **FSP (FTSO & FDC) Rewards:** Calculated per reward epoch (every 3.5 days, starting Mon/Thu). Requires meeting minimal conditions requirements. Published data is usually available shortly after epoch finalization at: - **GitHub:** **[flare-foundation/fsp-rewards](https://github.com/flare-foundation/fsp-rewards/tree/main/flare)** repository (look for files named by epoch). - **Forum:** The **[FSP Rewards Data thread](https://forum.flare.network/t/fsp-rewards-data)** on the Flare Discourse Forum often has summary files (like XLSX) and related discussions. To inspect your entity's FSP rewards for a specific epoch via CLI: 1. Get your `IDENTITY_ADDRESS` and the `EPOCH_ID` from the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/providers). ```bash IDENTITY_ADDRESS="0x5...1A8" # <-- REPLACE WITH YOUR Identity ADDRESS EPOCH_ID="288" # <-- REPLACE WITH THE REWARD EPOCH YOU WANT TO CHECK ``` 2. Use the following command to fetch and display your FSP rewards: ```bash curl -s "https://raw.githubusercontent.com/flare-foundation/fsp-rewards/refs/heads/main/flare/${EPOCH_ID}/reward-distribution-data.json" | \ jq --arg identity_address "${IDENTITY_ADDRESS}" \ '.rewardClaims[] | select(.body.beneficiary == ($identity_address | ascii_downcase)) | (.body.amount | tonumber / 1000000000000000000) | "FSP Rewards: \((. * 100 | floor) / 100) FLR"' ```
F3. How does the pass system work with minimal conditions? The pass system is an incentive mechanism for Flare Entities. Each entity has a number of passes, which can be gained or lost based on their performance in meeting the minimum participation requirements for each protocol within a reward epoch. Here's how it works: - **Starting Passes:** Newly registered entities begin with zero passes. - **Gaining Passes:** When an entity successfully meets all the [minimum conditions](/network/fsp/rewarding#minimal-conditions) in a reward epoch, they gain one pass. - **Maximum Passes:** An entity can hold a maximum of three passes. - **Losing Passes:** If an entity fails to meet the minimum condition for a protocol in a reward epoch, they will lose one pass. It's important to note that entities can lose multiple passes for failing across multiple protocols within that reward epoch. - **Consequences of Losing Passes:** - If an entity has one pass and fails to meet one minimal condition, they will be reduced to zero passes but will still receive FSP rewards for that epoch. - However, if an entity has zero passes and fails to meet a minimal condition, they will forfeit all FSP rewards for that epoch. For information on where to find reward data, see **FAQ F2**. Refer to **FAQ F1** to check how many passes your Flare Entity has. :::warning[Missing registration] If you are fail to register for a reward epoch your Flare Entity will lose all passes, as well as all FSP rewards for that reward epoch. :::
F4. How can I monitor the health of my Flare Entity? Consistent monitoring is key to maintaining performance and reward eligibility. Here are primary areas and methods: **1. Validator node health:** - Monitor the `/ext/health` endpoint for validator status - Check validator connectivity using this [API endpoint](https://build.avax.network/docs/rpcs/p-chain#platformgetcurrentvalidators) to verify uptime/connected values - For advanced monitoring, use Avalanche's [Grafana dashboards](https://support.avax.network/en/articles/6159074-grafana-dashboards) which are compatible with Flare **2. FDC health monitoring:** - Query the health endpoint exposed by each FDC verifier service you run: `GET http://{verifier-host}:{verifier-port}/verifier/{chain}/health` (Replace placeholders with your verifier's host, port, and the relevant chain like `btc`, `doge`, `xrp`, `eth`). Look for a success status/response. **3. FTSO health monitoring:** - Check minimal conditions status via the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/providers?tab=minimalConditions) or the backend API detailed previously in the FAQ. - Monitor the logs of your running Docker containers (`docker compose logs `) for errors and key operational messages: - **Registration:** Look for `RegisterVoter success` messages in the `system-client` logs around the start of reward epochs. ```plaintext # Example Registration Log Entry (Timestamp/Address vary) INFO epoch/registry_utils.go:187 Voter 0x... registered for epoch ... ``` - **Submissions:** Look for `Submitter ... successfully sent tx` messages in `system-client` logs during voting periods. Absence for >10 mins may indicate issues. ```plaintext # Example Submission Log Entries (Timestamp/Type vary) INFO protocol/submitter.go:76 Submitter submitSignatures successfully sent tx INFO protocol/submitter.go:76 Submitter submit1 successfully sent tx ``` **Alerting Tip:** Set alerts if no successful submission messages appear for 5-10 minutes during active voting periods. - **Finalization:** Look for `Relaying finished for protocol ... with success` messages in `system-client` logs after voting periods. Non-fatal errors may occur but should be investigated if persistent. (Protocol 100 = FTSOv2, Protocol 200 = FDC). ```plaintext # Example Finalization Log Entries (Timestamp/Status vary) INFO finalizer/relay_client.go:168 Relaying finished for protocol 100 with success INFO finalizer/relay_client.go:168 Relaying finished for protocol 200 with success ``` **Alerting Tip:** Set alerts if no successful finalization messages appear for protocols 100/200 within 5-10 minutes after a voting round should have completed.
F5. How can I opt-out of receiving FlareDrops? :::warning[FlareDrops have ended] FlareDrops concluded on January 30, 2026, completing the 36-month distribution schedule defined under [FIP.01](https://proposals.flare.network/FIP/FIP_1.html). WFLR, rFLR, and staked FLR no longer accrue the FlareDrop reward component. However, **protocol-level rewards remain unchanged**: you continue to earn rewards for FTSO delegation, FLR staking, and FAssets agent participation. Learn more about [FLR's operational utility era](https://flare.network/news/beyond-flaredrops-flr-enters-operational-utility-era). ::: To opt-out of receiving all future FlareDrops, you need to call the [`optOutOfAirdrop`](/network/solidity-reference/IDistributionToDelegators#optoutofairdrop) method on the [`DistributionToDelegators`](/network/solidity-reference) contract with your `Identity` address.
F6. How do I get real-time Slack/Discord/Telegram alerts for issues with my Flare Entity? To receive instant notifications about critical validator or data provider issues - such as FTSO or FDC reveal offenses, registration failures, and more - use the [flare-foundation/fsp-observer](https://github.com/flare-foundation/fsp-observer) monitoring service. 1. Create a env file (e.g., `.env.fsp-observer`) with your configuration: ```plaintext title=".env.fsp-observer" # Required RPC_BASE_URL="https://flare-api.flare.network" # Flare RPC endpoint IDENTITY_ADDRESS="0x0000000000000000000000000000000000000000" # your identity address # Optional notification channels NOTIFICATION_DISCORD_WEBHOOK="https://discord.com/api/webhooks/..." NOTIFICATION_TELEGRAM_BOT_TOKEN="your-telegram-bot-token" NOTIFICATION_TELEGRAM_CHAT_ID="your-telegram-chat-id" NOTIFICATION_SLACK_WEBHOOK="https://hooks.slack.com/services/..." NOTIFICATION_GENERIC_WEBHOOK="http://host:port/path" # generic POST webhook ``` :::warning Make sure this env file is **not committed** to version control (add it to `.gitignore` if needed). ::: 2. Run the observer using Docker: ```bash docker run --env-file .env.fsp-observer ghcr.io/flare-foundation/fsp-observer:main ``` The container will watch your identity and send notifications to any configured channels when issues are detected. If you encounter problems or have feature requests, open an issue on the [GitHub issues page](https://github.com/flare-foundation/fsp-observer/issues).
--- ## GCP Marketplace Nodes Deploy blockchain nodes without the need to install dependencies or manage configuration files. The nodes come with a copy of the databases locally which drastically reduces bootstrap times. The machine images are available for Flare Mainnet, Flare Testnet Coston2, Songbird Canary-Network, Songbird Testnet Coston and several other networks. Head to [Google Cloud Marketplace](https://console.cloud.google.com/marketplace/product/flare-public/blockchain-machine-image-flare-network) to get started. ## Supported blockchain nodes | Name | Config Dir | Machine Type | vCPUs | RAM | Disk Size | Disk Type | | :---------------------------- | :--------------------- | :------------- | :---- | :---- | :-------- | :-------- | | Flare Mainnet | `/etc/flare` | n2d-standard-4 | 4 | 16 GB | 830 GB | Balanced | | Flare Mainnet Rosetta | `/etc/flare_rosetta` | n2d-standard-4 | 4 | 16 GB | 830 GB | Balanced | | Flare Testnet Coston2 | `/etc/coston2` | n2d-standard-4 | 4 | 16 GB | 280 GB | Balanced | | Flare Testnet Coston2 Rosetta | `/etc/coston2_rosetta` | n2d-standard-4 | 4 | 16 GB | 280 GB | Balanced | | Songbird Canary-Network | `/etc/songbird` | n2d-standard-8 | 8 | 32 GB | 2,730 GB | Balanced | | Songbird Testnet Coston | `/etc/coston` | n2d-standard-4 | 4 | 16 GB | 280 GB | Balanced |
Other supported networks | Name | Config Dir | Machine Type | vCPUs | RAM | Disk Size | Disk Type | | :------------------ | :--------------- | :----------------- | :---- | :---- | :-------- | :-------- | | Algorand | `/etc/algorand` | e2-custom-8-13056 | 8 | 13 GB | 70 GB | Balanced | | Avalanche | `/etc/avalanche` | e2-custom-8-16384 | 8 | 16 GB | 530 GB | Balanced | | Bitcoin | `/etc/bitcoin` | e2-custom-2-16128 | 2 | 16 GB | 1,030 GB | Balanced | | Binance Smart Chain | `/etc/bsc` | c2-standard-8 | 8 | 32 GB | 2,030 GB | SSD | | Cosmos Hub | `/etc/cosmos` | n2d-standard-4 | 4 | 16 GB | 1,030 GB | Balanced | | Dogecoin | `/etc/dogecoin` | e2-custom-2-11008 | 2 | 11 GB | 380 GB | Balanced | | Ethereum | `/etc/ethereum` | n2d-standard-8 | 8 | 32 GB | 2,230 GB | Balanced | | Ethereum Holesky | `/etc/ethereum` | n2d-standard-8 | 8 | 32 GB | 430 GB | Balanced | | Ethereum Sepolia | `/etc/ethereum` | n2d-standard-8 | 8 | 32 GB | 730 GB | Balanced | | Filecoin Lotus | `/etc/filecoin` | e2-custom-16-32768 | 16 | 32 GB | 1,030 GB | SSD | | Litecoin | `/etc/litecoin` | e2-custom-2-11520 | 2 | 12 GB | 330 GB | Balanced | | Polygon | `/etc/polygon` | n2d-standard-16 | 16 | 64 GB | 6,030 GB | SSD | | XRPL | `/etc/xrpl` | n2d-standard-8 | 8 | 32 GB | 375 GB | Local SSD |
## Prerequisites Ensure you have: - A Google Cloud account - A service account with at least the following permissions (it can be created beforehand or during the launch process using GUI): - **roles/config.agent** - **roles/compute.admin** - **roles/iam.serviceAccountUser** - Verify that your [Quotas and System Limits](https://docs.cloud.google.com/docs/quotas/view-manage), located in **IAM and admin > Quotas and system limits**, meet the resource requirements for the blockchain node you intend to deploy. Refer to the [Supported blockchain nodes](#supported-blockchain-nodes) table for the specific resource requirements of each node. Adjust your quotas as needed to ensure sufficient resources are available. ## Setup a node 1. **Locate and launch the blockchain machine image** Head to the [Google Cloud Marketplace](https://console.cloud.google.com/marketplace/product/flare-public/blockchain-machine-image-flare-network). Click the **Launch** button to proceed. 2. **Configure basic settings** Choose the service account, source image, and region for your instance. To use an existing service account, click the **Existing account** button. Next, select the network where the node will be deployed, and configure basic firewall rules. Once done, click **Deploy**. This will launch a node with the deployment name, for example `flare-node`, in the **Compute Engine > VM instances**. 3. **Troubleshooting** If you encounter any issues during the deployment process, refer to the [Troubleshooting Section](#troubleshooting) for guidance. ## Connect to the node Node operations are managed using the `nodectl` CLI tool. ```bash sudo nodectl help ``` Configuration files are located in the `/etc/` directory. For instance, Flare nodes will have their configurations in `/etc/flare`. To start the node, connect to the instance and apply the services using: ```bash # Applies all services configured in `/etc//config.yaml` sudo nodectl apply --target all ``` ## Verify node operation The quickest way to verify that the node is running is by using the built-in health checks. Logs can also be accessed in the `/var/log/` directory, for example, `/var/log/flare`. ```bash # Run health checks sudo nodectl health # Display detailed node information sudo nodectl status ``` For live log monitoring, use `nodectl`: ```bash sudo nodectl logs -f ``` For more log options, refer to the `journalctl` manual: ```bash man journalctl ``` ## Advanced Deployment with Terraform Blockchain machine images can also be deployed using Terraform to meet advanced requirements, such as replicating nodes, automating configuration, and scaling. In this section, we provide an example which deploys two replicas of Flare nodes in the EU and US regions. Source code is available in the [flare-foundation/bmi-terraform-examples](https://github.com/flare-foundation/bmi-terraform-examples) repository. The first step is to obtain the latest blockchain machine image. Start by navigating to the [Google Cloud Marketplace](https://console.cloud.google.com/marketplace/product/flare-public/blockchain-machine-image-flare-network) and click `LAUNCH`. Then, switch to the `COMMAND-LINE DEPLOYMENT` tab and scroll down to the VM Image section, where you will find a table containing the latest images. Copy the name of the latest image and update the locals in the `instance.tf` file accordingly. Additionally, ensure that all required variables are correctly set in the `terraform.tfvars`, such as GCP project name, before proceeding. ```hcl locals { replicas = { 1 = { # update here image = "projects/mpi-flare-public/global/images/flare---v1-..." zone = "europe-west1-c" }, 2 = { # update here image = "projects/mpi-flare-public/global/images/flare---v1-..." zone = "us-west1-a" } } } ``` Next, configure a firewall rule to allow external traffic from other peers in the network to access port `9651`. ```hcl resource "google_compute_firewall" "peering" { project = var.gcp_project_name name = "flare-example-peering-tcp" network = "default" allow { protocol = "tcp" ports = ["9651"] } source_ranges = ["0.0.0.0/0"] target_tags = ["flare-example-peering"] priority = "600" } ``` Optionally, you can create a service account with the necessary permissions to access a Slack webhook stored in GCP Secret Manager. The node uses the secret's name to obtain the webhook and sends health alerts to Slack. Each instance must define both startup and shutdown scripts. The startup script handles node configuration tasks, such as enabling Promtail log scraping, node metrics, and Slack alerts. Use tools such as `jq`, `yq` and `sed` to perform the configuration. ```bash #!/bin/bash #### Configure node #### # Example yq eval '.health_checks.slack_alerts.webhook_secret_name = "example_slack_webhook_secret_name"' -i /etc/flare/config.yaml yq eval '.health_checks.slack_alerts.enabled = true' -i /etc/flare/config.yaml #### Start node #### nodectl apply --target all ``` The shutdown script ensures the node shuts down gracefully. ```bash #!/bin/bash nodectl stop ``` Finally, all the previously mentioned files and resources are used to define instance resources, which tie together the configuration, scripts, replication and permissions necessary for the node deployment. ```hcl resource "google_compute_instance" "this" { for_each = local.replicas project = var.gcp_project_name name = "flare-node-example-replica-${each.key}" zone = each.value.zone machine_type = "n2d-standard-4" metadata = { startup-script = templatefile("${path.module}/templates/startup.sh", {}) shutdown-script = templatefile("${path.module}/templates/shutdown.sh", {}) } boot_disk { initialize_params { image = each.value.image type = "pd-balanced" } } network_interface { network = "default" access_config { // Ephemeral public IP } } tags = google_compute_firewall.peering.target_tags service_account { email = google_service_account.this.email scopes = ["cloud-platform"] } } ``` ## Troubleshooting - **Deployment via UI failure due to Terraform state lock** - **Symptom**: The deployment fails, and clicking **Retry** leads to Terraform state lock errors. - **Solution**: Instead of retrying, delete the failed deployment and start a new one. - **Deployment failure due to exceeded quotas** - **Symptom**: The deployment fails, displaying an error message in the logs similar to: ``` Error: Error waiting for instance to create: Quota 'SSD_TOTAL_GB' exceeded. Limit: 500.0 in region us-west1. metric name = compute.googleapis.com/ssd_total_storage limit name = SSD-TOTAL-GB-per-project-region limit = 500 dimensions = map[region:us-west1] ``` - **Solution**: Resource quotas need to be increased manually. To do this, refer to the [Quotas and System Limits documentation](https://docs.cloud.google.com/docs/quotas/view-manage) and navigate to **IAM & Admin > Quotas**. After adjusting the quotas, delete the failed deployment and deploy a new one. --- ## Audits Flare places the highest priority on the security and reliability of its network and protocols. To ensure integrity, independent, reputable security firms conduct thorough audits of node software, smart contracts, and critical offchain components. Below is a list of completed audits, grouped by protocol or component. Each report provides detailed insights into the scope of the audit, methodologies used, findings, and remediation status where applicable. ### [Smart Accounts](/smart-accounts/overview) | Scope / Description | Auditor | Date | Report | | ---------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------- | | FAsset Redeem Composer | Zellic | Apr 2026 | [Link](@site/static/pdf/audits/2026-04-24-FAsset_Redeem_Composer-Zellic_Audit_Report.pdf) | | Smart Accounts Diff v2 | Zellic | Apr 2026 | [Link](@site/static/pdf/audits/2026-04-23-Smart_Accounts-Diff_Zellic_Audit_Report.pdf) | | Flare Smart Accounts Diff v1 | Zellic | Feb 2026 | [Link](@site/static/pdf/audits/2026-02-12-Zellic-Smart_Accounts_diff_v1.pdf) | | Flare Smart Accounts | Zellic | Nov 2025 | [Link](@site/static/pdf/audits/2025-11-26-Zellic-Smart_Accounts_Audit_Report.pdf) | ### [FAssets & FXRP](/fassets/overview) | Scope / Description | Auditor | Date | Report | | --------------------------------------- | ------------ | --------- | ------------------------------------------------------------------------------------------------------------ | | FAssets | OpenZeppelin | Jan 2026 | [Link](@site/static/pdf/audits/20260128-OpenZeppelin-FAssets.pdf) | | FAssets Diff v3 | Zellic | Nov 2025 | [Link](@site/static/pdf/audits/20251125-Zellic-Flare-FAssets-diff-v3.pdf) | | FAssets Diff v2 | Zellic | Oct 2025 | [Link](@site/static/pdf/audits/20251020-Zellic-Flare_FAssets_diff-v2.pdf) | | FAssets Diff v1 | Zellic | Sep 2025 | [Link](@site/static/pdf/audits/20250923_Zellic-Flare-FAssets-diff-v1.pdf) | | Fasset OFTAdapter | Zellic | Aug 2025 | [Link](@site/static/pdf/audits/20250409-Zellic-FAsset_OFTAdapter_Report.pdf) | | FAsset Smart Contracts v1.2 | Zellic | Aug 2025 | [Link](@site/static/pdf/audits/20250801-Zellic-Flare_FAssets_Audit_Report_08_2025.pdf) | | FAsset Immunefi Audit Comp | Immunefi | June 2025 | [Link](https://immunefi.com/audit-competition/audit-comp-flare-fassets/leaderboard/) | | FAssets v1.1 | Coinspect | Apr 2025 | [Link](@site/static/pdf/audits/20250401-Coinspect-SmartContractAudit-Flare-FAssetCoreVault-v250506.pdf) | | FAssets Smart Contract Audit V2 Updates | Coinspect | Dec 2024 | [Link](@site/static/pdf/audits/20241215-Coinspect-SmartContractAudit-Flare-FAssetV2Updates-v241217.pdf) | | FAssets Smart Contract Audit (Update) | Coinspect | Sep 2024 | [Link](@site/static/pdf/audits/20240901-Coinspect-SmartContractAudit-Flare-FassetUpdate-v240910.pdf) | | FAsset Liquidator | Coinspect | Dec 2023 | [Link](@site/static/pdf/audits/20231207-Coinspect-Flare-Smart_Contract_Review-FAsset_Liquidator-v231207.pdf) | | FAsset V2 Bots | Coinspect | Oct 2023 | [Link](@site/static/pdf/audits/20231001-Coinspect-Flare-Source_Code_Review-FAsset_Bots-v240220.pdf) | | FAsset V2 Smart Contracts | Coinspect | Sep 2023 | [Link](@site/static/pdf/audits/20230901-Coinspect-Flare-Smart_Contract_Review-FAsset_V2-v240220.pdf) | | FAssets V1 Smart Contracts | Coinspect | Jun 2022 | [Link](@site/static/pdf/audits/20220601-Coinspect_Smart_Contract_Audit_fAsset_v220829.pdf) | ### [FTSO](/ftso/overview) | Scope / Description | Auditor | Date | Report | | -------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------- | | FTSOv2 Custom Feeds Diff Review | Coinspect | Feb 2025 | [Link](@site/static/pdf/audits/20250205-Coinspect-SmartContractAudit-Flare-FTSOv2CustomFeedsDiffReview-v240210.pdf) | | FTSO Fast Updates Implementation | Halborn | May 2024 | [Link](@site/static/pdf/audits/20240501-Halborn-FastUpdatesAudit.pdf) | | FTSO Fast Updates Protocol | Halborn | May 2024 | [Link](@site/static/pdf/audits/20240501-Halborn-FTSOFastUpdatesprotocolAudit.pdf) | | FTSO V2 Fast Updates Code Review | Coinspect | Apr 2024 | [Link](@site/static/pdf/audits/20240401-Coinspect-Flare-SourceCodeSecurityReview-FastUpdates-v240612.pdf) | | FTSO V2 Scaling Code Review | Coinspect | Jan 2024 | [Link](@site/static/pdf/audits/20240101-Coinspect-Flare-SourceCodeSecurityReview-FTSOScaling-v240515.pdf) | | FTSO V1 Hybrid Reward Band | Coinspect | Jan 2023 | [Link](@site/static/pdf/audits/20230101-Coinspect-SmartContractAudit-FlareHybridBandRewardv230220.pdf) | ### [FDC & Attestations](/fdc/overview) | Scope / Description | Auditor | Date | Report | | --------------------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | Web2Json Attestation v2 | Coinspect | Oct 2025 | [Link](@site/static/pdf/audits/20251001-Coinspect-SourceCodeAudit-Flare-Web2JSONAttestationv2-FixReview-v251208.pdf) | | FlareDA Layer (FTSO & FDC) | FYEO | Oct 2024 | [Link](@site/static/pdf/audits/20241001-FLARE-SecurityCodeReviewFLAREDataAvailabilityv1.0_Public.pdf) | | Verifier Servers and FDC Updates | Coinspect | Oct 2024 | [Link](@site/static/pdf/audits/20241001-Coinspect-SourceCodeAudit-Flare-FDCv2-v250113.pdf) | | FDC Client | Coinspect | Aug 2024 | [Link](@site/static/pdf/audits/20240801-Coinspect-Source_Code_Audit-Flare-FDCv1-v241004.pdf) | | Transaction Verifier | Coinspect | Mar 2024 | [Link](@site/static/pdf/audits/20240301-Coinspect-Flare-SourceCodeSecurityReview-TransactionVerifier-v240516.pdf) | | Attestation Suite Smart Contracts | Coinspect | Jan 2024 | [Link](@site/static/pdf/audits/20240101-Coinspect-Smart_Contract_Review-Flare_Attestation_Suite-v240220.pdf) | | Attestation Client V1 | Coinspect | Jun 2022 | [Link](@site/static/pdf/audits/20220601-Coinspect-Flare-Source_Code_Review-Attestation_Client-v240220.pdf) | ### Staking | Scope / Description | Auditor | Date | Report | | ---------------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | Staking P2 Offchain Services | Coinspect | Oct 2023 | [Link](@site/static/pdf/audits/20231001-Coinspect-Flare-Source_Code_Review-Staking_P2-Offchain_Services-v240220.pdf) | | Staking P2 Smart Contracts | Coinspect | Sep 2023 | [Link](@site/static/pdf/audits/20230901-Coinspect-Flare-Smart_Contract_Review-StakingP2-v240220.pdf) | ### Core protocol, validators & infrastructure | Scope / Description | Auditor | Date | Report | | ----------------------------------------- | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | Node update to 1.10 X-Chain bug | FYEO | Mar 2025 | [Link](@site/static/pdf/audits/20250301-FYEO-FlareOngoingFxIndexFix.pdf) | | Lowering gas limit for Songbird | FYEO | June 2024 | [Link](@site/static/pdf/audits/20240601-FYEO-Flare-Ongoing_Songbird_June_20th_1.0_Public.docx.pdf) | | Flare upgrade to Avalanche 1.9.0 | FYEO | June 2024 | [Link](@site/static/pdf/audits/20240601-FYEO-Flare-Ongoing_Songbird_June_10th_1.0_Public.pdf) | | Songbird codebase integration into Flare | FYEO | May 2024 | [Link](@site/static/pdf/audits/20240501-FYEO-FlareOngoingSongbird1.0_Public.pdf) | | Flare Systems Client (Top level client) | Coinspect | Jan 2024 | [Link](@site/static/pdf/audits/20240101-Coinspect-Flare-SourceCodeSecurityReview-TopLevelClient-v240515.pdf) | | CChain Indexer | Coinspect | Jan 2024 | [Link](@site/static/pdf/audits/20240101-Coinspect-Flare-SourceCodeSecurityReview-CChainIndexer-v240409.pdf) | | Golang Validator Flare Network | FYEO | Feb 2023 | [Link](@site/static/pdf/audits/20230201-FlareNetworksLtd.-SecureCodeReviewof_Golang_Validator_on_the_Flare_Network_v1.0_February_Public.pdf) | | Validator Codebase V3 | FYEO | Dec 2022 | [Link](@site/static/pdf/audits/20221201-FlareNetworksLtd.-Dec2022-SecureCodeReviewofSoliditySmartContractsontheFlareNetworkv1.0_Public.pdf) | | Smart Contracts V1 Audit 3 (Dec 22) | FYEO | Dec 2022 | [Link](@site/static/pdf/audits/20221201-FlareNetworksLtd.-Dec2022-SecureCodeReviewofSoliditySmartContractsontheFlareNetworkv1.0_Public.pdf) | | Flare TDE Updates | Coinspect | Dec 2022 | [Link](@site/static/pdf/audits/20221201-Coinspect-SmartContractAudit-FlareTDEUpdates-v221220.pdf) | | Smart Contracts V1 Audit 2 (Oct 22) | FYEO | Oct 2022 | [Link](@site/static/pdf/audits/20221001-Flare_Networks_Ltd.-Secure_Code_Review_of_Solidity_Smart_Contracts_on_the_Flare_Network_v1.0_Public.pdf) | | Flare Airdrop (Update) | Coinspect | Oct 2022 | [Link](@site/static/pdf/audits/20221001-Coinspect-Smart_Contract_Audit-Flare_Airdrop_Update-v221109.pdf) | | Validator Codebase V2 | FYEO | Sep 2022 | [Link](@site/static/pdf/audits/20220901-Flare_Networks_Ltd.-Secure_Code_Review_of_the_Flare_Validator_V2-Report_v1.0_Public.pdf) | | Smart Contracts V1 Audit 1 (Sep 22) | FYEO | Sep 2022 | [Link](@site/static/pdf/audits/20220901-Flare_Networks_Ltd.-Secure_Code_Review_of_Solidity_Contracts_on_the_Flare_Network_v1.0_public.pdf) | | Validator Codebase | FYEO | Aug 2022 | [Link](@site/static/pdf/audits/20220801-Flare_Networks_Ltd.-Secure_Code_Review_of_the_Flare_Network's_Validator_Codebase_v1.0_Public.pdf) | | Multi Chain Library | Coinspect | Jun 2022 | [Link](@site/static/pdf/audits/20220601-Coinspect-Flare-Source_Code_Review-Multichain_Client_Library-v240220.pdf) | | Core Smart Contracts (Network Launch) | Coinspect | Jun 2022 | [Link](@site/static/pdf/audits/20220601-Coinspect-Smart_Contract_Audit-Flare_Network_Launch.pdf) | | Flare Smart Contracts V1 Audit 2 (Mar 22) | Coinspect | Mar 2022 | [Link](@site/static/pdf/audits/20220201-Coinspect-Smart_Contract_Audit-Flare.pdf) | | Flare Smart Contracts V1 Audit 1 (Jul 21) | Coinspect | Jul 2021 | [Link](@site/static/pdf/audits/20210701-Coinspect-Smart_Contract_Audit-Flare.pdf) | ### Other protocols & contracts | Scope / Description | Auditor | Date | Report | | ------------------------------ | --------- | --------- | ------------------------------------------------------------------------------------------------------------------- | | USDT Swapper | Coinspect | Feb 2025 | [Link](@site/static/pdf/audits/20250201-Coinspect-SmartContractAudit-Flare-USDT_Swapper-v250327.pdf) | | rNat (rFLR) smart contracts | Coinspect | June 2024 | [Link](@site/static/pdf/audits/20240601-Coinspect-SmartContractSecurityReview-Flare-RNatContracts-v240701.pdf) | | Hex Wrapped Tokens | Coinspect | Nov 2023 | [Link](@site/static/pdf/audits/20231101-Coinspect-Flare-SmartContractAudit-HexWrappedTokens-v231211.pdf) | | Smart Contract Preregistration | Coinspect | Jan 2025 | [Link](@site/static/pdf/audits/20250101-Coinspect-SmartContractAudit-Flare-Pre-Register&FTSOManagement-v250109.pdf) | _Disclaimer: Security audits identify potential vulnerabilities at a specific point in time based on the code provided. They do not guarantee the complete absence of future bugs or vulnerabilities._ --- ## FAQs This page answers common questions encountered by developers building applications on Flare networks. ## Network & Tokens ### Where can I get testnet tokens (faucet)? Flare provides faucets for its public testnets: - **Flare Testnet Coston2:** Get C2FLR, FXRP, and USDT0 from the [Coston2 Faucet](https://faucet.flare.network/coston2). Use Coston2 for testing applications intended for Flare mainnet. - **Songbird Testnet Coston:** Get CFLR from the [Coston Faucet](https://faucet.flare.network/coston). Use Coston for testing applications intended for the Songbird canary network. ### How do I add Flare/Songbird networks to my wallet (e.g., MetaMask)? The easiest way is to visit the respective block explorer, click on **Connect** in the top right corner, and approve adding the network to your wallet. - **Flare Mainnet:** [Flare Explorer](https://flare-explorer.flare.network/) - **Coston2 Testnet:** [Coston2 Explorer](https://coston2-explorer.flare.network/) - **Songbird Canary-Network:** [Songbird Explorer](https://songbird-explorer.flare.network/) - **Coston Testnet:** [Coston Explorer](https://coston-explorer.flare.network/) Alternatively, you can add them manually using the details on the [Network Configuration](/network/overview#configuration) page. ### What's the difference between Flare and Songbird? - **Flare (FLR):** The main production network with real economic value. - **Songbird (SGB):** A "canary network" - blockchain used for testing protocol-level features and dApps in a live environment with real consequences before they are potentially deployed to Flare. It serves as a proving ground. Read more on the [Network Overview](/network/overview) page. ### Which RPC endpoint should I use? - **For Development/Testing:** The [Public RPCs](/network/overview#configuration) are convenient for getting started. However, they have rate limits and shared resources. - **For Production Applications:** It is **strongly recommended** to use a dedicated RPC endpoint from a provider for better performance, reliability, and support. See the [Developer Tools](/network/developer-tools#rpcs) page for options. - **For Running Nodes:** You can also run your own [RPC node](/run-node#rpc-node). ### What is the block time and finality on Flare? Flare networks use the Snowman++ consensus protocol. - **Block Time:** Approximately 1.8 seconds. - **Finality:** Near-instantaneous (single-slot finality). Once a block is accepted by consensus, it's considered final. Learn more on the [Consensus](/network/consensus) page. ## Development Resources ### Where can I find the addresses of Flare's core smart contracts? The addresses for contracts like `WNat`, `FtsoV2`, `FdcHub`, etc., vary by network. 1. **Use the `FlareContractRegistry`:** This contract acts as a directory. It has the **same address** (`0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019`) on **all** Flare networks (Flare, Coston2, Songbird, Coston). You can query its [`getContractAddressByName`](/network/solidity-reference/IFlareContractRegistry#getcontractaddressbyname) or [`getAllContracts`](/network/solidity-reference/IFlareContractRegistry#getallcontracts) methods to find the addresses of other contracts on the specific network you are connected to. 2. **Check the Solidity Reference:** Deployed contract addresses for each network are listed on the relevant reference pages for convenience: - [Network Reference](/network/solidity-reference) - [FSP Reference](/network/fsp/solidity-reference) - [FTSOv2 Reference](/ftso/solidity-reference) - [Scaling Reference](/ftso/scaling/solidity-reference) - [FDC Reference](/fdc/reference) - [FAssets Reference](/fassets/reference) ### Are there official developer tools or SDKs? Flare maintains several resources to aid development: - **Language-Specific Guides:** Tutorials for interacting with Flare using [JavaScript](/network/guides/flare-for-javascript-developers), [Python](/network/guides/flare-for-python-developers), [Go](/network/guides/flare-for-go-developers), and [Rust](/network/guides/flare-for-rust-developers). - **Starter Kits:** Pre-configured project templates for [Hardhat & Foundry](/network/guides/hardhat-foundry-starter-kit). - **Developer Tools List:** A curated list of [RPCs, Indexers, Bridges, SDKs, and more](/network/developer-tools). ### Where can I learn about Flare-specific terminology? Learn more about the terminology used in the Flare ecosystem (like FTSO, FSP, PDA, etc.) on the [Terminology](/support/terminology) page. ## Community & Support ### I have a project I would like to build on Flare. Can I get a grant? Yes! The Flare Foundation offers grants to support innovative projects that contribute to the ecosystem's growth. Learn more and apply on the [Flare Grants Program](https://flare.network/resources/grants) page. ### Where can I get technical support or ask questions? Connect with Flare's developer community and the team through these channels: - **Flare Experts:** Get dedicated [Technical Support](https://flare.network/resources/technical-support) from a Flare expert. - **Telegram:** The primary channel for community Q&A and developer discussions. Join [Flare Network on Telegram](https://t.me/FlareNetwork). - **GitHub:** Report issues, contribute to code, or explore the open-source repositories at [Flare Foundation GitHub](https://github.com/flare-foundation/). (Best for code-specific issues, not general Q&A). --- ## FLR FLR is the native token of the Flare network and serves several key functions: - **Network Security**: Staking to validators to provide network security through a [Proof-of-Stake (PoS) consensus](/network/consensus). - **Data Provision**: Incentivized delegation to the [Flare Time Series Oracle (FTSO)](/ftso/overview). - **Governance**: Participation within network [governance](/network/governance). - **Transaction Fees**: Prevention of spam attacks, all transaction fees are [burned](/network/overview#transaction-format). ## FLR details | Attribute | Description | | ---------------------------------- | --------------- | | **Network** | Flare Mainnet | | **Token** | FLR | | **Decimals** | 18 | | **Genesis creation date** | July 14, 2022 | | **Token Distribution Event (TDE)** | January 9, 2023 | :::tip[Obtaining testnet tokens] FLR is required to make transactions on Flare Mainnet. You can acquire Coston and Coston2 testnet tokens using the [faucets](/network/overview#configuration). ::: ## Genesis token allocation At network genesis, 100 billion FLR tokens (100,000,000,000) were created. The majority of tokens are intended for community ownership, whether through direct token distribution, network incentives, or the Flare Foundation ecosystem programs. :::info [FIP.01](https://proposals.flare.network/FIP/FIP_1.html) lowered the initial token supply to 15 billion FLR tokens. [Burned backer tokens](https://flare.network/news/flare-to-burn-2-1-billion-flr-tokens-to-support-ecosystem-health): From October 2024, 66,293,390 (66 million) FLR is set to be burned monthly until January 2026, increasing the community allocation from 58.3% to 59.6%. ::: :::warning[FlareDrops have ended] FlareDrops concluded on January 30, 2026, completing the 36-month distribution schedule defined under [FIP.01](https://proposals.flare.network/FIP/FIP_1.html). WFLR, rFLR, and staked FLR no longer accrue the FlareDrop reward component. However, **protocol-level rewards remain unchanged**: you continue to earn rewards for FTSO delegation, FLR staking, and FAssets agent participation. Learn more about [FLR's operational utility era](https://flare.network/news/beyond-flaredrops-flr-enters-operational-utility-era). ::: These tokens have been allocated to the following groups. Note the delegation, claiming, and voting abilities of each, defined here: - **Can delegate**: The entity can earn standard inflationary rewards by delegating tokens to the [Flare Time Series Oracle (FTSO)](/ftso/overview). - **Can claim**: The entity can wrap tokens to claim a portion of the [FlareDrop](/network/guides/manage-flaredrops). - **Can vote**: The entity can use its tokens to participate in governance by voting on [Flare Improvement Proposals (FIPs)](/network/governance#flare-improvement-proposals-fips). ### Flare community | Entity | Total FLR Allocation | Can Delegate | Can Claim the FlareDrop | Can Vote | | ------------------------------ | --------------------------------- | ------------ | ----------------------- | -------- | | **Flare Foundation** | 9,787,578,628(9.8 billion) | ❌ | ❌ | ❌ | | **Initial Token Distribution** | 4,278,738,206(4.3 billion) | ✅ | ✅ | ✅ | | **FlareDrop** | 24,246,183,166(24.2 billion) | ✅ | ✅ | ✅ | | **Incentive Pool** | 20,000,000,000(20 billion) | ✅ | ✅ | ✅ | | **Subtotal** | 58.3 billion FLR | - | - | - | ### Flare partners | Entity | Total FLR Allocation | Can Delegate | Can Claim the FlareDrop | Can Vote | | ------------------- | ---------------------------------- | ------------ | ----------------------- | -------- | | **Flare Labs (FL)** | 12,965,300,324(12.97 billion) | ✅ | ❌ | ✅ | | **Flare VC Fund** | 10,000,000,000(10 billion) | ✅(\*) | ❌ | ❌ | | **Subtotal** | 22.97 billion FLR | - | - | - | (\*) With tokens from FL only. ### Team, advisors, and backers | Entity | Total FLR Allocation | Can Delegate | Can Claim the FlareDrop | Can Vote | | ----------------- | ------------------------------- | ------------ | ----------------------- | -------- | | **Founding Team** | 7,000,000,000(7 billion) | ✅ | ❌ | ✅ | | **Rest of Team** | 1,500,000,000(1.5 billion) | ✅ | ❌ | ✅ | | **Future Team** | 3,000,000,000(3 billion) | ✅ | ❌ | ✅ | | **Advisors** | 2,000,000,000(2 billion) | ✅ | ✅ | ✅ | | **Backers** | 3,100,811,196(3.1 billion) | ✅ | ✅ | ✅ | | **Subtotal** | 16.6 billion FLR | - | - | - | ### Inflation amount | Entity | Total FLR Allocation | Can Delegate | Can Claim the FlareDrop | Can Vote | | ------------- | -------------------- | ------------ | ----------------------- | -------- | | **Inflation** | N/A | ✅ | ✅ | ✅ | ## Public distribution & inflation The 28,524,921,372 FLR public distribution is split into two parts: 1. The first 15%, the initial Airdrop (4,278,738,206 FLR), was distributed during the Token Distribution Event (TDE) on January 9, 2023, to wallets that held XRP on December 12, 2020. 2. The remaining 85% (24,246,183,166 FLR), the FlareDrop, are being distributed over 36 monthly amounts directly to token holders who have wrapped their FLR into WFLR. ### Inflation under FIP.01 Under [FIP.01](https://proposals.flare.network/FIP/FIP_1.html), annual inflation was defined as a percentage of circulating supply on a stepped schedule: - **Year 1:** 10% - **Year 2:** 7% - **Year 3 onward:** 5% ### FIP.16 and ongoing inflation (2026) [FIP.16: Restructure FLR Tokenomics for Long-Term Network Sustainability](https://proposals.flare.network/FIP/FIP_16.html) was approved by governance (voting concluded April 24, 2026). It changes the ongoing economics of inflation and related mechanisms. Rollout is phased — some parameters take effect soon after the vote, while others require a network hard fork and coordinated releases. | Parameter | Before FIP.16 | After FIP.16 (target) | | ------------------------- | ------------- | --------------------- | | Annual inflation rate | 5% | 3% | | Annual inflation hard cap | 5 billion FLR | 3 billion FLR | The proposal also revises which token balances count toward the inflatable supply used in the percentage calculation. For example, excluding certain temporarily or permanently unavailable pools, such as the burn address and Flare Income Reinvestment pools. This can further reduce realized inflation versus headline percentages. Inflation rewards are planned one reward cycle ahead of distribution. On Flare, a common rule of thumb is one cycle of roughly four epochs, or about 14 days, for inflation-funded rewards such as FSP, staking, and legacy FTSOv1 buckets. The annualized percentage in analytics is typically computed from each cycle’s minted inflation versus circulating supply. After governance execution, dashboards move to the new target as cycle parameters update. For live supply, emissions, burns, and staking, use the [FLR Tokenomics Dashboard](https://dune.com/flare/tokenomics). :::note[FIP.16 items not yet fully live] The following were approved in FIP.16 but depend on forks, contract redeployments, or further rollout. Confirm current status in the proposal, release notes, and the tokenomics dashboard. - **Transaction fees:** All native FLR paid as transaction fees are burned, which is already true on Flare. FIP.16 additionally proposes a substantial increase in base transaction fees. The proposal describes a 20× increase in the minimal base gas fee after the v1.13.0-related fee path, alongside [ACP-176](https://github.com/flare-foundation/go-flare) parameters. See FIP.16 §3 for the exact parameter story and illustrative cost table. - **FIRE (Flare Income Reinvestment Entity):** Framework for routing protocol revenues (for example from FDC, FAssets, Flare Smart Accounts, Flare Confidential Compute (FCC), and captured MEV) toward supply reduction and ecosystem growth. - **Protocol-owned block building and MEV:** Staged roadmap toward a verifiable builder model with on-chain MEV capture (FIP.16 §4.4). - **P-Chain staking and provider economics:** Higher relative weight for P-Chain stake in signing weight, larger per-validator stake cap, and a minimum 20% infrastructure provider (entity) fee share on relevant rewards (FIP.16 §5). - **Fee redirection:** Portions of fees from FDC attestations, FAssets minting and redemption paths, and FCC flows directed to the protocol incentive side (FIRE or incentive pool) as described in FIP.16 §4. ::: ## Supply metrics The recipients of the initial token distribution (the Airdrop) make up the largest group of FLR holders. These tokens are immediately available for use in the network, such as participating in governance or delegating to the Flare Time Series Oracle (FTSO). The allocations to the Flare Foundation and Flare Labs also include tokens reserved for backers who have already unlocked, although their distribution does not begin until month six. Flare team members can use their tokens to actively participate in the network and help provide reliable FTSO data. However, they are restricted from selling any of their tokens for the first six months and may sell no more than 25% within the first 18 months. The percentages shown for the Flare Foundation and Flare Labs include these unlocked, but not yet distributed, backer tokens. In total, 80.2% of the 100 billion tokens in the distributed supply are eligible to vote in governance. The remaining 19.8% - held by the Flare Foundation and the Flare VC Fund - is not permitted to vote. ## Distribution schedule By the end of the 36-month token distribution period, 93.9 billion FLR tokens will be liquid and in circulation. After the initial 15% distribution, most recipients receive the remaining allocation gradually and consistently over the full 36 months. While these tokens can be used for network participation, such as governance and delegation to the Flare Time Series Oracle (FTSO), they are not considered liquid during the vesting period. Restrictions on team token sales mean that, until month 19, there are fewer liquid tokens than there are tokens eligible to participate in governance and FTSO delegation. Once the distribution is complete, 85% of the total FLR supply (93.9 billion out of 110.1 billion) will be in circulation. --- ## Terminology ## Account An account is a record in the Flare ledger that holds data and facilitates the sending and receiving of tokens. Each account is identified by a unique address derived from a public key. An account can be either an [externally owned account (EOA)](#externally-owned-account-eoa) or a [smart contract](#smart-contract). ## Address A unique identifier representing an account on the Flare network. Addresses are derived from public keys and are used to send and receive tokens. ## API Application Programming Interface. A set of rules and protocols that allow different software applications to communicate with each other, enabling integration and interaction. ## Block A collection of transactions bundled together and added to the blockchain. Blocks are created by validators and are used to record transactions on the network. ## Blockchain A distributed ledger that records transactions across multiple computers in a secure and tamper-proof way. Each block in the blockchain contains a list of transactions and a reference to the previous block, creating a continuous chain of blocks. ## Bridge A protocol that connects independent blockchains, enabling interoperability and the transfer of assets and information between them. ## Canary A network used for testing features under “real fire” conditions before deploying them on the mainnet. Users of the canary network are real users but are aware of the experimental nature of the platform. The term originates from the practice of using canaries in mines to detect poisonous gas. Flare's canary network is called Songbird. ## Consensus The process by which a network of nodes agrees on the validity of transactions and the state of the ledger. Consensus mechanisms secure the network and prevent double-spending. ## Delegated Proof of Stake (DPoS) A consensus mechanism in which token holders vote for validators to secure the network and validate transactions. Validators are elected based on the number of votes they receive from token holders. ## Enshrined Oracle Enshrining involves incorporating essential middleware components directly into the core protocol of a blockchain network. For a component to be considered enshrined, it **must** require a network hard-fork to be altered (refer to Vitalik's article on [enshrinement](https://vitalik.eth.limo/general/2023/09/30/enshrinement.html)). Enshrining can be seen as a stronger form of restaking, a concept popularized by protocols like [EigenLayer](https://docs.eigencloud.xyz). Unlike restaking, where only a fraction of the network stake is used to secure middleware, enshrining leverages the entire network stake. An enshrined oracle is an oracle that is integrated into the core protocol of a blockchain network. These oracles inherit the full security of the network, meaning that compromising the safety or liveness of the oracle would necessitate compromising the entire network. ## EVM Ethereum Virtual Machine. A virtual machine that runs smart contracts on the Ethereum network. The EVM is a Turing-complete machine that executes code written in Solidity or other programming languages. ## Externally Owned Account (EOA) An account controlled by a private key and can send transactions on the network. EOAs are typically used by individuals to manage their tokens. ## Fork A change in the protocol of a blockchain network that results in two separate chains. Forks can be caused by software updates, consensus rule changes, or network upgrades. ## Gas A unit of measurement representing the computational work required to execute a transaction on the network. Gas is used to pay for transaction fees and prevent spam. ## Hard Fork A type of fork that is not backward-compatible with the existing protocol. Hard forks require all nodes to upgrade to the new protocol to continue participating in the network. ## Node A computer that participates in the Flare network by running software that validates transactions and maintains a copy of the blockchain. Nodes can be validators, data providers, or regular network participants. ## Oracle A service that provides offchain data to smart contracts on the blockchain. Oracles bring real-world data, such as price feeds, weather information, and sports scores, to the blockchain. ## Smart Contract An account controlled by code that can send transactions on the network. Smart contracts are self-executing contracts with the terms of the agreement directly written into lines of code. ## Token A digital asset representing ownership of a physical or virtual asset. Tokens can represent a wide range of assets, including cryptocurrencies, real estate, and intellectual property. ## Transaction A record of a transfer of tokens or data on the blockchain. Transactions are signed by the sender and include information such as the recipient's address, the amount of tokens transferred, and the gas fee. ## Validator A node that participates in the consensus process and validates transactions on the network. Validators are responsible for creating new blocks and securing the network. ## Wallet A software application that allows users to store, send, and receive tokens. Wallets can manage multiple accounts and interact with decentralized applications on the blockchain. --- ## Whitepapers Explore Flare's whitepapers, research, and analytics to gain deeper insights into its technology. ## Featured ## All Research