# Getting Started

> Learn how to verify data from other chains using FDC.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

Source: https://dev.flare.network/fdc/getting-started

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.

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[​](#process-overview "Direct link to 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[​](#identify-the-transaction "Direct link to 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.

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.

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[​](#prepare-the-attestation-request "Direct link to 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[​](#request-structure "Direct link to Request structure")

To prepare an attestation request, you can use the `prepareRequest` endpoint with the following JSON structure:

```
{  "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[​](#implementation-example "Direct link to Implementation example")

Here's a TypeScript script that prepares the attestation request:

prepare\_request.ts

```
// Simple hex encodingfunction toHex(data) {  let result = "";  for (let i = 0; i < data.length; i++) {    result += data.charCodeAt(i).toString(16);  }  return result.padEnd(64, "0");}const VERIFIER_BASE_URL = "https://fdc-verifiers-testnet.flare.network/";const VERIFIER_API_KEY = "XXX"; // Your API keyconst TX_ID =  "0x4e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c";async function prepareRequest() {  const attestationType = "0x" + toHex("EVMTransaction");  const sourceType = "0x" + toHex("testETH");  const requestData = {    attestationType: attestationType,    sourceId: sourceType,    requestBody: {      transactionHash: TX_ID,      requiredConfirmations: "1",      provideInput: true,      listEvents: true,      logIndices: [],    },  };  const response = await fetch(    `${VERIFIER_BASE_URL}/verifier/eth/EVMTransaction/prepareRequest`,    {      method: "POST",      headers: {        "X-API-KEY": VERIFIER_API_KEY,        "Content-Type": "application/json",      },      body: JSON.stringify(requestData),    },  );  const data = await response.json();  console.log("Prepared request:", data);  return data;}prepareRequest().then((data) => {  console.log("Prepared request:", data);  process.exit(0);});
```

### Verifier response[​](#verifier-response "Direct link to Verifier response")

Upon successful validation, the verifier returns:

```
{  "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.

<details>
<summary>Understanding the structure of abiEncodedRequest.</summary>

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:

```
45564d5472616e73616374696f6e00000000000000000000000000000000000074657374455448000000000000000000000000000000000000000000000000009d410778cc0b2b8f1b8eaa79cbd0eed5d3be7514dea070e2041ad00a4c6e88f800000000000000000000000000000000000000000000000000000000000000204e636c6590b22d8dcdade7ee3b5ae5572f42edb1878f09b3034b2f7c3362ef3c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000
```

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`.

</details>

## Submit the attestation request[​](#submit-the-attestation-request "Direct link to 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`:

submit\_request.ts

```
import { ethers } from "hardhat";import { interfaceToAbi } from "@flarenetwork/flare-periphery-contract-artifacts";// In production get the data directly from FlareSystemsManagerconst firstVotingRoundStartTs = 1658429955;const votingEpochDurationSeconds = 90;// Valid only on coston. In production get the address from the ContractRegistryconst FDC_HUB_ADDRESS = "0x1c78A073E3BD2aCa4cc327d55FB0cD4f0549B55b";async function submitRequest() {  const requestData = await prepareRequest();  const abi = interfaceToAbi("IFdcHub", "coston");  const fdcHub = await ethers.getContractAt(abi, FDC_HUB_ADDRESS);  // Call to the FDC Hub protocol to provide attestation.  const tx = await fdcHub.requestAttestation(requestData.abiEncodedRequest, {    value: ethers.parseEther("1"),  });  const receipt = await tx.wait();  // Get block number of the block containing contract call  const blockNumber = receipt.blockNumber;  const block = await ethers.provider.getBlock(blockNumber);  // Calculate roundId  const roundId = Math.floor(    (block!.timestamp - firstVotingRoundStartTs) / votingEpochDurationSeconds,  );  console.log(    `Check round progress at: https://coston-systems-explorer.flare.rocks/voting-round/${roundId}?tab=fdc`,  );  return roundId;}submitRequest();
```

After submitting the request, wait for round finalization before proceeding to proof extraction and verification.

## Extract proof and data[​](#extract-proof-and-data "Direct link to 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[​](#using-the-da-layer-api "Direct link to 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.

```
{  "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:

get\_proof.ts

```
const DA_LAYER_URL = "DA_LAYER_URL";const TARGET_ROUND_ID = 123; // The round id we want to get the proof for (the one we calculated when we submitted the request)async function getProof(roundId: number) {  const request = await prepareRequest();  const proofAndData = await fetch(    `${DA_LAYER_URL}/api/v0/fdc/get-proof-round-id-bytes`,    {      method: "POST",      headers: {        "Content-Type": "application/json",        "X-API-KEY": API_KEY,      },      body: JSON.stringify({        votingRoundId: roundId,        requestBytes: request.abiEncodedRequest,      }),    },  );  return await proofAndData.json();}getProof(TARGET_ROUND_ID)  .then((data) => {    console.log("Proof and data:");    console.log(JSON.stringify(data, undefined, 2));  })  .catch((e) => {    console.error(e);  });
```

### Response structure[​](#response-structure "Direct link to 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:

```
{  "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[​](#verify-and-use-the-data "Direct link to 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[​](#data-structure "Direct link to 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:

```
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[​](#implementation-example-1 "Direct link to 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.sol

```
// SPDX-License-Identifier: MITpragma solidity ^0.8.25;import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";import {IEVMTransaction} from "@flarenetwork/flare-periphery-contracts/coston2/IEVMTransaction.sol";import {IFdcVerification} from "@flarenetwork/flare-periphery-contracts/coston2/IFdcVerification.sol";struct TokenTransfer {    address from;    address to;    uint256 value;}interface ITransferEventListener {    function collectTransferEvents(        IEVMTransaction.Proof calldata _transaction    ) external;}contract TransferEventListener is ITransferEventListener {    TokenTransfer[] public tokenTransfers;    // USDC contract address on sepolia    address public constant USDC_CONTRACT =        0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238;    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 (                // The topic0 doesn't match the Transfer event                _event.topics.length == 0 || // No topics                _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            // the 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;    }    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 true;        return fdc.verifyEVMTransaction(transaction);    }}
```

[Open in Remix](https://remix.ethereum.org/#url=https://github.com/flare-foundation/developer-hub/blob/main/examples/developer-hub-solidity/FDCTransferEventListener.sol&version=builtin&evmVersion=cancun&optimize=true&runs=200)

  

warning

Don't forget to set the EVM version to `cancun` in Remix before compiling the contract.

### Using the contract[​](#using-the-contract "Direct link to 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:

verify\_proof.ts

```
import { ethers } from "hardhat";const EVENT_COLLECTOR_ABI = "...";const EVENT_COLLECTOR_ADDRESS = "...";async function submitProof() {  const dataAndProof = await getProof(TARGET_ROUND_ID);  const transferEventListener = await ethers.getContractAt(    EVENT_COLLECTOR_ABI,    EVENT_COLLECTOR_ADDRESS,  );  const tx = await transferEventListener.collectTransferEvents({    merkleProof: dataAndProof.proof,    data: dataAndProof.response,  });  console.log(tx.hash);  console.log(await transferEventListener.getTokenTransfers());}submitProof()  .then(() => {    console.log("Submitted proof");  })  .catch((e) => {    console.error(e);  });
```

## Wait for round finalization (optional)[​](#wait-for-round-finalization-optional "Direct link to 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

```
isFinalized(uint256 _protocolId, uint256 _votingRoundId) returns (bool)
```

## Watch the video[​](#watch-the-video "Direct link to Watch the video")
