Flare Studio Logo
Analytics Dashboard

Complete Web2JSON Guide: From Setup to Production

Official Flare documentation with complete setup instructions, real terminal outputs, and beginner-friendly explanations.

Quick Start: Complete Setup Guide

Step 1: Clone and Setup

bash
# Clone the official Flare Hardhat starter
git clone https://github.com/flare-foundation/flare-hardhat-starter
cd flare-hardhat-starter

# Install all dependencies
npm install

Step 2: Environment Configuration

Create a .env file in the project root:

bash
# .env - Fill in these values
PRIVATE_KEY=0xYourPrivateKeyHere
COSTON2_RPC_URL=https://coston2-api.flare.network/ext/C/rpc
FLARE_RPC_URL=https://flare-api.flare.network/ext/C/rpc
VERIFIER_API_KEY_TESTNET=00000000-0000-0000-0000-000000000000
ATTESTATION_PROVIDER_URL_TESTNET="https://web2json-verifier-test.flare.rocks/"
WEB3JSON_VERIFIER_URL_TESTNET=https://fdc-verifiers-testnet.flare.network/
COSTON2_DA_LAYER_URL="https://ctn2-data-availability.flare.network/"

Step 3: Compile Contracts

bash
# Compile all smart contracts
npx hardhat compile

Web2Json Attestation Type

The Web2Json attestation type enables data collection from an arbitrary Web2 source. You can learn more about it in the official specification.

Info: The Web2Json attestation type is currently only available on the Flare Testnet Coston2.

We will now demonstrate how the FDC protocol can be used to collect the data of a given Star Wars API 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. We will define a scripts/fdcExample/Web2Json.ts file that will encapsulate the whole process.

Complete Main Script Structure

typescript
import { run, web3 } from "hardhat";
import { StarWarsCharacterListV2 } from "../../typechain-types";

import {
    prepareAttestationRequestBase,
    submitAttestationRequest,
    retrieveDataAndProofBaseWithRetry,
} from "../utils/fdc";

const StarWarsCharacterListV2 = artifacts.require("StarWarsCharacterListV2");

const {
    ATTESTATION_PROVIDER_URL_TESTNET,
    VERIFIER_API_KEY_TESTNET,
    COSTON2_DA_LAYER_URL
} = process.env;

// yarn hardhat run scripts/fdcExample/Web2Json.ts --network coston2

// 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"}`;

const attestationTypeBase = "Web2Json";
const sourceIdBase = "PublicWeb2";
const verifierUrlBase = ATTESTATION_PROVIDER_URL_TESTNET;

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}Web2Json/prepareRequest`;
    const apiKey = VERIFIER_API_KEY_TESTNET;

    return await prepareAttestationRequestBase(url, apiKey, attestationTypeBase, sourceIdBase, requestBody);
}

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 retrieveDataAndProofBaseWithRetry(url, abiEncodedRequest, roundId);
}

async function deployAndVerifyContract() {
    const args: any[] = [];
    const characterList: StarWarsCharacterListV2Instance = await StarWarsCharacterListV2.new(...args);
    try {
        await run("verify:verify", {
            address: characterList.address,
            constructorArguments: args,
        });
    } catch (e: any) {
        console.log(e);
    }
    console.log("StarWarsCharacterListV2 deployed to", characterList.address, "\n");
    return characterList;
}

async function interactWithContract(characterList: StarWarsCharacterListV2Instance, proof: any) {
    console.log("Proof hex:", proof.response_hex, "\n");

    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");
}

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(() => {
    process.exit(0);
});

Explanation: This is the main script that coordinates the entire Web2JSON workflow. It prepares the request, submits it to the blockchain, retrieves the verified data, and interacts with your smart contract.

Run Web2JSON Example

bash
# Execute the complete Web2JSON workflow
npx hardhat run scripts/fdcExample/Web2Json.ts --network coston2

Expected Time: 2-4 minutes total. The script will wait for blockchain finalization automatically.

What You'll See When Running the Script:

terminal
Web2JSON terminal output

Live terminal output showing the complete Web2JSON workflow

terminal
Web2JSON terminal output

Output Explanation

text
$ npx hardhat run scripts/fdcExample/Web2Json.ts --network coston2

🔹 Step 1: Preparing attestation request...
Url: https://web2json-verifier-test.flare.rocks/Web2Json/prepareRequest 

Prepared request:
 {
  attestationType: '0x576562324a736f6e000000000000000000000000000000000000000000000000',
  sourceId: '0x5075626c69635765623200000000000000000000000000000000000000000000',
  requestBody: {
    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"}'
  }
} 

Response status is OK

Data: {
  status: 'VALID',
  abiEncodedRequest: '0x576562324a736f6e0000...}

🔹 Step 2: Submitting to blockchain...
Submitted request: 0x4796959e6ad0d697a3acc2ba39463186015f94f05ead7e25d88514e0f07dc937 
Block timestamp: 1762606158n 
First voting round start ts: 1658430000n 
Voting epoch duration seconds: 90n
Calculated round id: 1157512
Received round id: 1157512 
Check round progress at: https://coston2-systems-explorer.flare.rocks/voting-round/1157512?tab=fdc

🔹 Step 3: Waiting for verification...
Url: https://ctn2-data-availability.flare.network/api/v1/fdc/proof-by-request-round-raw n
Waiting for the round to finalize...
Round finalized!

🔹 Step 4: Retrieving proof...
Waiting for the DA Layer to generate the proof...
Proof generated!

Proof: {
  response_hex: '0x00000000000..',
  attestation_type: '0x576562324a736f6e000000000000000000000000000000000000000000000000',
  proof: [
    '0x4305b2025b90e3316dfdadb6f08a4fc6daaa685253f6218529266708b83c16e0',
    '0x2bdf5b10f6027a520f1e99f1aecb61909b3708639cb1cd4209f2e45a125e550a',
    '0xff12aaa4fcb39d21c5086c9e8da732b0e811d4f655037f011f4a1ead08be9e8c',
    '0xadd7610ccf4a2fcea7564b116ae597d12fe22ac1abee1a8010c8685c760f4000'
  ]
}

🔹 Step 5: Deploying contract...
StarWarsCharacterListV2 deployed to 0x9eE...d92d5904Ce5d65B01 

🔹 Step 6: Interacting with contract...
Transaction: 0xcb9cd16bd195661584ee55a8b76cb72674....

Star Wars Characters:
 [
  [
    'R2-D2',
    '6',
    '3',
    '34',
    name: 'R2-D2',
    numberOfMovies: '6',
    apiUid: '3',
    bmi: '34'
  ]
]

 Web2JSON workflow completed successfully!

What Just Happened:

  • • Fetched R2-D2 data from Star Wars API
  • • Flare network verified the data
  • • Got cryptographic proof of authenticity
  • • Stored verified data in your contract
  • • Calculated BMI from verified height/mass

Timing Expectations:

  • • Request submission: ~15 seconds
  • • Round finalization: 1-3 minutes
  • • Proof generation: ~30 seconds
  • • Total time: 2-4 minutes

Web2JSON Step-by-Step Process :

Step 1: Prepare Your Data Request

Simple Explanation: This is where you tell Flare exactly what data you want and how you want it formatted. Think of it like giving detailed instructions to a food delivery service.

Food Delivery Analogy:
• Regular order: "Give me some food" → You get whatever the chef decides
• Web2JSON order: "Cheeseburger, no onions, extra pickles, cut in half" → You get exactly what you asked for

What You're Telling Flare:

Where to Get Data

"Go to this website URL and fetch the data for me"

How to Process It

"Extract only these specific fields and format them this way"

Required Information

  • attestationType - "I want Web2 data" (always "Web2Json"")
  • sourceId - "From public websites" (always "PublicWeb2")
  • requestBody - Your specific data instructions

The requestBody tells Flare exactly what to do:

json
{
  "url": "Where to get the data (website address)",
  "httpMethod": "How to fetch it (GET, POST, etc.)", 
  "headers": "Any special headers needed",
  "queryParams": "URL parameters to include",
  "body": "Data to send for POST requests",
  "postProcessJq": "Which fields to extract and how to format them",
  "abiSignature": "How to package the data for blockchain use"
}

Real Example: Getting R2-D2's Info

Here's what we're actually doing in our Star Wars example:

typescript
//  WHERE to get data
const apiUrl = "https://swapi.info/api/people/3";
// ↑ "Go to Star Wars API and get character #3 (R2-D2)"

//  WHAT data to extract and HOW to format it
const postProcessJq = `{
  name: .name,                    // "Get the character's name"
  height: .height,                // "Get their height as a number"  
  mass: .mass,                    // "Get their weight as a number"
  numberOfFilms: .films | length, // "Count how many films they appear in"
  uid: (.url | split("/") | .[-1] | tonumber) // "Extract their ID number from the URL"
}`;

//  HOW to package for blockchain
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"
}`;
// ↑ "Package the data in this exact structure for the blockchain"

What Happens Behind the Scenes:

  1. 1. Flare goes to the Star Wars API and gets ALL of R2-D2's data
  2. 2. It extracts ONLY the fields we specified (name, height, mass, etc.)
  3. 3. It converts the data to the exact format we requested
  4. 4. It gives us back a "package" ready for blockchain use

Key Point: The "Prepare Request" step doesn't actually fetch the data yet. It just creates a "shopping list" that tells Flare exactly what to get and how to prepare it when we're ready.

Step 2 : Text to Hex Conversion

Simple Explanation: Blockchain computers don't understand normal text like "Web2Json". They only understand numbers and hex codes. These functions translate human words into computer language.

Language Translation Analogy:
Human: "Web2Json" (English)
Computer: "0x576562324a736f6e0.." (Computer language)

Why We Need This:

Without Encoding

Blockchain sees: "Web2Json" → Doesn't understand!

With Encoding

Blockchain sees: "0x576562324a736f6e..." → Perfect!

Step 1: Convert Text to Hex (toHex)

This function takes each letter and converts it to its computer number equivalent.

typescript
function toHex(data: string) {
  var result = "";
  
  // Go through each letter one by one
  for (var i = 0; i < data.length; i++) {
    // Convert: W → 57, e → 65, b → 62, etc.
    result += data.charCodeAt(i).toString(16);
  }
  
  // Make sure it's exactly 64 characters long by adding zeros
  return result.padEnd(64, "0");
}

Real Example:

text
Input: "Web2Json"
Step-by-step conversion:
W → 57
e → 65  
b → 62
2 → 32
J → 4a
s → 73
o → 6f
n → 6e

Result: "576562324a736f6e" + "000000000000000000000000000000000000000000000000"
Final: "576562324a736f6e000000000000000000000000000000000000000000000000"

Step 2: Add "0x" Prefix (toUtf8HexString)

Blockchain computers expect hex codes to start with "0x" so they know it's computer language.

typescript
function toUtf8HexString(data: string) {
  // Just add "0x" to the beginning
  return "0x" + toHex(data);
}

Final Result:

text
Input: "Web2Json"
Output: "0x576562324a736f6e000000000000000000000000000000000000000000000000"

Input: "PublicWeb2"  
Output: "0x5075626c6963576562320000000000000000000000000000000000000000000000"

Where This Is Used:

  • attestationType: Convert "Web2Json" → hex code
  • sourceId: Convert "PublicWeb2" → hex code
  • Any text field that needs to be understood by the blockchain

Why 64 Characters? Blockchain computers expect exactly 32 bytes of data (64 hex characters). The padding with zeros ensures everything fits perfectly in the computer's memory.

The Magic: When you see those long hex codes in your terminal output, these functions are what created them! They're the translators between human language and computer language.

Step 3: Send Your Request to Flare

Simple Explanation: Now that you've prepared your "shopping list," it's time to send it to Flare's verification service. This is like giving your detailed order to the restaurant kitchen.

Restaurant Analogy:
Step 1: Write your order (Prepare Request)
Step 2: Give it to the cashier (Send to Verifier)
Step 3: Get order number (ABI Encoded Request)

What's Happening Here:

Package Your Order

Wrap up your request with all the details

Send to Kitchen

Post your request to Flare's verifier

Get Order Number

Receive ABI encoded request for blockchain

The Main Function: prepareAttestationRequestBase

This is the core function that sends your request to Flare. Think of it as the "cashier" who takes your order to the kitchen.

typescript
async function prepareAttestationRequestBase(
  url: string,                    // Where to send the request
  apiKey: string,                 // Your Flare API key (like restaurant membership)
  attestationTypeBase: string,    // "Web2Json" - type of data you want
  sourceIdBase: string,           // "PublicWeb2" - where data comes from  
  requestBody: any,               // Your detailed order from Step 1
) {
  console.log("Url:", url, "\n");
  
  // Convert text to hex format that blockchain understands
  const attestationType = toUtf8HexString(attestationTypeBase);
  const sourceId = toUtf8HexString(sourceIdBase);

  // Package everything together
  const request = {
    attestationType: attestationType,
    sourceId: sourceId,
    requestBody: requestBody,     // Your Star Wars API instructions
  };
  console.log("Prepared request:\n", request, "\n");

  // Send to Flare's verifier
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "X-API-KEY": apiKey,        // Your API key for authentication
      "Content-Type": "application/json",
    },
    body: JSON.stringify(request), // Your order in JSON format
  });
  
  // Check if request was accepted
  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");

  // Get your "order number" (ABI encoded request)
  return await response.json();
}

What You See in Terminal:

text
Url: https://web2json-verifier-test.flare.rocks/Web2Json/prepareRequest
Prepared request: { ...your detailed order... }
Response status is OK
Data: { 
  status: 'VALID', 
  abiEncodedRequest: '0x576562324a736f6e00...' 
}

The abiEncodedRequest is your "order number" - you'll use this in the next step!

Simple Wrapper Function

To make things easier, we create a simple function that handles the Web2JSON-specific details:

typescript
async function prepareAttestationRequest(
  apiUrl: string,           // Star Wars API URL
  postProcessJq: string,    // Which fields to extract
  abiSignature: string,     // How to format the data
) {
  // Package your specific Web2JSON order
  const requestBody = {
    url: apiUrl,
    httpMethod: "GET",
    headers: "{}",
    queryParams: "{}", 
    body: "{}",
    postProcessJq: postProcessJq,
    abiSignature: abiSignature,
  };

  // Send to the right "counter" (Web2Json endpoint)
  const url = `${verifierUrlBase}Web2Json/prepareRequest`;
  const apiKey = VERIFIER_API_KEY_TESTNET;

  // Let the cashier handle the rest
  return await prepareAttestationRequestBase(
    url,
    apiKey,
    "Web2Json",     // Always Web2Json for this type of data
    "PublicWeb2",   // Always PublicWeb2 for public APIs
    requestBody,
  );
}

Why Two Functions?

  • prepareAttestationRequestBase: Generic "cashier" that works for all data types
  • prepareAttestationRequest: Specialized for Web2JSON orders specifically
  • This separation makes the code cleaner and reusable for different data types

Success Check: When this step works, you'll see "Response status is OK" and get back an abiEncodedRequest. This means Flare has accepted your order and is ready to process it in the next step!

Step 4: Submit to Blockchain

Simple Explanation: Now we take our "order number" from Flare and actually submit it to the blockchain. This is like paying for your order and getting a receipt.

Payment Analogy:
Step 1: Write order (Prepare Request)
Step 2: Give to cashier (Send to Verifier)
Step 3: Pay and get receipt (Submit to Blockchain)

What's Happening in This Step:

Pay Gas Fee

Pay transaction fee to use the blockchain

Get Receipt

Transaction hash proves you submitted

Get Wait Time

Round ID tells you how long to wait

The "Helpers" - Flare's Toolbox

Flare provides a special "toolbox" contract that helps us interact with their system. Think of it as a universal remote control for all Flare services.

solidity
contract Helpers {
    // These are like different "buttons" on your remote control:
    
    function getFdcHub() public view returns (IFdcHub) {
        return ContractRegistry.getFdcHub();           // "Submit request" button
    }

    function getFdcRequestFeeConfigurations() public view returns (IFdcRequestFeeConfigurations) {
        return ContractRegistry.getFdcRequestFeeConfigurations(); // "Check price" button
    }

    function getFlareSystemsManager() public view returns (IFlareSystemsManager) {
        return ContractRegistry.getFlareSystemsManager();         // "Check time" button
    }

    function getRelay() public view returns (IRelay) {
        return ContractRegistry.getRelay();                       // "Verify completion" button
    }
}

Why Helpers? Instead of remembering complicated addresses, we use this "remote control" to easily access all Flare services.

Main Submission Function

This is where we actually submit our request to the blockchain and pay the fee.

typescript
async function submitAttestationRequest(abiEncodedRequest: string) {
  // Get the "submit request" button from our remote control
  const fdcHub = await getFdcHub();

  // Check how much this request costs
  const requestFee = await getFdcRequestFee(abiEncodedRequest);

  // Submit to blockchain and pay the fee
  const transaction = await fdcHub.requestAttestation(abiEncodedRequest, {
    value: requestFee,  // Pay the required fee
  });
  console.log("Submitted request:", transaction.tx, "\n");

  // Find out which "voting round" we're in
  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;  // This tells us how long to wait
}

What You See in Terminal:

text
Submitted request: 0xe9a316fce6bf67210f2f90b2c868baf0a0fb3d9f8bf694bd7193d0645544923d
Block timestamp: 1762603316
First voting round start ts: 1658430000  
Voting epoch duration seconds: 90
Calculated round id: 1157481
Check round progress at: https://coston2-systems-explorer.flare.rocks/voting-round/1157481?tab=fdc

Checking the Fee

typescript
async function getFdcRequestFee(abiEncodedRequest: string) {
  // Use the "check price" button from our remote control
  const helpers = await getHelpers();
  const feeConfigAddress = await helpers.getFdcRequestFeeConfigurations();
  const feeConfig = await FdcRequestFeeConfigurations.at(feeConfigAddress);
  
  // Ask "How much does this request cost?"
  return await feeConfig.getRequestFee(abiEncodedRequest);
}

Calculating Wait Time (Round ID)

Flare processes requests in "voting rounds" that happen every 90 seconds. We need to figure out which round our request is in.

typescript
async function calculateRoundId(transaction: any) {
  // Check when our transaction happened
  const blockNumber = transaction.receipt.blockNumber;
  const block = await ethers.provider.getBlock(blockNumber);
  const blockTimestamp = BigInt(block!.timestamp);

  // Use the "check time" button from our remote control
  const flareSystemsManager = await getFlareSystemsManager();
  const firstRoundStart = BigInt(await flareSystemsManager.firstVotingRoundStartTs());
  const roundDuration = BigInt(await flareSystemsManager.votingEpochDurationSeconds());

  // Calculate: (Current time - First round time) ÷ 90 seconds
  const roundId = Number(
    (blockTimestamp - firstRoundStart) / roundDuration
  );
  
  console.log("Calculated round id:", roundId, "\n");
  return roundId;  // This is like getting your "batch number"
}

Real Example from Your Terminal:

  • Transaction Time: 1762603316 (when you submitted)
  • First Round Ever: 1658430000 (when Flare system started)
  • Round Duration: 90 seconds (each voting period)
  • Calculation: (1762603316 - 1658430000) ÷ 90 = 1157481
  • Meaning: Your request is in voting round #1,157,481

Success Check: When this step works, you'll see a transaction hash and round ID. This means your request is officially on the blockchain and waiting to be processed by Flare's network! The script will now automatically wait for the voting round to complete.

Step 5: Use the Verified Data

Simple Explanation: This is the final step where we actually use the verified Star Wars data in our smart contract. Think of it as receiving your verified package and storing it safely.

Package Delivery Analogy:
Step 1-4: Order, pay, wait for delivery, verify package
Step 5: Open package and use the contents

What We're Building:

Digital Collection

A smart contract that stores verified Star Wars character data

Trusted Data

Only accepts data verified by Flare's network

Data Structures - Our "Storage Boxes"

First, we define what data we want to store:

solidity
// What we store in our collection
struct StarWarsCharacter {
    string name;           // Character name (R2-D2)
    uint256 numberOfMovies; // How many films they appeared in
    uint256 apiUid;        // Unique ID from Star Wars API  
    uint256 bmi;           // Calculated BMI (Body Mass Index)
}

// How data arrives from Flare (temporary package)
struct DataTransportObject {
    string name;           // Name from API
    uint256 height;        // Height in cm
    uint256 mass;          // Weight in kg
    uint256 numberOfMovies; // Film count
    uint256 apiUid;        // API ID number
}

BMI Calculation:

We calculate BMI from the verified height and mass: (mass * 100 * 100) / (height * height)
This shows how you can derive new information from verified data!

The Main Contract - Our "Collection Store"

This smart contract acts like a digital collection that only accepts verified Star Wars character data.

solidity
contract StarWarsCharacterList {
    // Our collection storage
    mapping(uint256 => StarWarsCharacter) public characters;  // Store by ID
    uint256[] public characterIds;                           // List of all IDs

    // STEP 1: Verify the data is authentic
    function isWeb2JsonProofValid(IWeb2Json.Proof calldata _proof) 
        private view returns (bool) 
    {
        // Ask Flare: "Is this data legit?"
        return ContractRegistry.getFdcVerification().verifyJsonApi(_proof);
    }

    // STEP 2: Add a new character to our collection
    function addCharacter(IWeb2Json.Proof calldata data) public {
        // First, verify the proof is valid
        require(isWeb2JsonProofValid(data), "Invalid proof");
        // ↑ If proof is fake, STOP everything!

        // STEP 3: Unpack the verified data
        DataTransportObject memory dto = abi.decode(
            data.data.responseBody.abi_encoded_data,
            (DataTransportObject)
        );
        // ↑ This extracts: name, height, mass, film count, ID

        // STEP 4: Check if we already have this character
        require(characters[dto.apiUid].apiUid == 0, "Character already exists");

        // STEP 5: Calculate BMI from verified height & mass
        uint256 bmi = (dto.mass * 100 * 100) / (dto.height * dto.height);

        // STEP 6: Create our character record
        StarWarsCharacter memory character = StarWarsCharacter({
            name: dto.name,
            numberOfMovies: dto.numberOfFilms,
            apiUid: dto.apiUid,
            bmi: bmi  // Our calculated value!
        });

        // STEP 7: Store in our collection
        characters[dto.apiUid] = character;
        characterIds.push(dto.apiUid);
    }

    // STEP 8: View all characters in our collection
    function getAllCharacters() public view returns (StarWarsCharacter[] memory) {
        StarWarsCharacter[] memory result = new StarWarsCharacter[](characterIds.length);
        
        // Get each character by their ID
        for (uint256 i = 0; i < characterIds.length; i++) {
            result[i] = characters[characterIds[i]];
        }
        
        return result;  // Returns array of all characters
    }
}

Real Example - R2-D2 Data:

json
// What Flare delivers (DataTransportObject):
{
  "name": "R2-D2",
  "height": 96,        // 96 cm
  "mass": 32,          // 32 kg  
  "numberOfFilms": 7,
  "apiUid": 3
}

// What we store (StarWarsCharacter):
{
  "name": "R2-D2",
  "numberOfMovies": 7,
  "apiUid": 3,
  "bmi": 3472          // (32 * 100 * 100) / (96 * 96)
}

Security Features

Proof Verification

Rejects any data not verified by Flare network

Duplicate Protection

Prevents adding the same character twice

The Magic Happens Here:

  1. 1. Trust: Flare verifies the Star Wars data is authentic
  2. 2. Calculation: We derive new information (BMI) from trusted data
  3. 3. Storage: Store permanently on blockchain
  4. 4. Access: Anyone can view but only add verified data

This demonstrates how Web2JSON lets you build applications that trustlessly use real-world data on the blockchain!

Step 6: Verify the Proof

Simple Explanation: This is where Flare proves that the data you received is authentic and hasn't been tampered with. Think of it like checking the security seal on your delivered package.

Package Security Analogy:
Regular delivery: Package arrives, you trust it's correct
Flare delivery: Package arrives with crypto-proof that it's authentic

How Flare's Proof System Works:

Merkle Trees

Like a family tree for data - each piece connects to prove authenticity

Gas Optimization

Store proofs on-chain, keep data off-chain to save costs

The Verification Process:

  1. Flare network fetches and processes the Star Wars data
  2. Creates cryptographic proof that this data is authentic
  3. Stores proof on-chain (cheap) but keeps data off-chain (expensive)
  4. Your contract verifies the proof matches Flare's records

Why This Matters:

  • Prevents fake data: No one can submit made-up Star Wars characters
  • Saves money: Storing proofs is much cheaper than storing full data
  • Maintains trust: Anyone can verify the data came from the real API

Deploying Our Collection Contract

typescript
async function deployAndVerifyContract() {
  const args: any[] = [];
  
  // Deploy our Star Wars collection contract
  const characterList = await StarWarsCharacterList.new(...args);
  
  try {
    // Verify contract on block explorer (optional)
    await run("verify:verify", {
      address: characterList.address,
      constructorArguments: args,
    });
  } catch (e: any) {
    console.log(e);  // If verification fails, just continue
  }
  
  console.log("StarWarsCharacterList deployed to", characterList.address, "\n");
  return characterList;
}

Step 7: Add R2-D2 to Your Collection

Simple Explanation: This is the final step where we take the verified R2-D2 data and add it to our blockchain collection. Think of it as placing your authenticated collectible in your display case.

typescript
async function interactWithContract(
  characterList: StarWarsCharacterListInstance,
  proof: any,
) {
  console.log("Proof hex:", proof.response_hex, "\n");

  //  STEP 1: Understand the data structure from Flare
  const IWeb2JsonVerification = await artifacts.require("IWeb2JsonVerification");
  const responseType = IWeb2JsonVerification._json.abi[0].inputs[0].components[1];
  console.log("Response type:", responseType, "\n");

  // STEP 2: Unpack the verified data
  const decodedResponse = web3.eth.abi.decodeParameter(responseType, proof.response_hex);
  console.log("Decoded proof:", decodedResponse, "\n");
  
  //  STEP 3: Add R2-D2 to our collection
  const transaction = await characterList.addCharacter({
    merkleProof: proof.proof,    // The crypto-proof from Flare
    data: decodedResponse,       // The actual R2-D2 data
  });
  
  console.log("Transaction:", transaction.tx, "\n");
  
  //  STEP 4: Display our complete collection
  console.log("Star Wars Characters:\n", await characterList.getAllCharacters(), "\n");
}

What You'll See in Terminal:

text
Proof hex: 0x...long_hex_string...
Response type: {components: [...], name: "task", type: "tuple"}
Decoded proof: {
  name: "R2-D2",
  height: 96,
  mass: 32,
  numberOfFilms: 7,
  uid: 3
}
Transaction: 0xabcd...1234
Star Wars Characters:
[
  {
    name: "R2-D2",
    numberOfMovies: 7,
    apiUid: 3,
    bmi: 3472
  }
]

Run the complete workflow with this command:

bash
npx hardhat run scripts/fdcExample/Web2Json.ts --network coston2

Complete Workflow Summary: You've now gone from requesting Star Wars data to having verified R2-D2 information stored permanently on the blockchain! This demonstrates the full power of Web2JSON for bringing real-world data to your smart contracts.

Common Issues & Solutions

❌ "Cannot find module" errors

bash
Error: Cannot find module '@flarenetwork/flare-periphery-contracts'

Solution: Run npm install in the project root directory.

❌ "Insufficient funds" error

text
Error: insufficient funds for gas * price + value

Solution: Get testnet FLR from Flare Faucet.

❌ "Response status is not OK" from verifier

text
Error: Response status is not OK, status 401 Unauthorized

Solution: Check your VERIFIER_API_KEY_TESTNET in .env file.

Script hangs at "Waiting for round to finalize"

This is normal! The Flare network takes 1-3 minutes to verify data. The script will continue automatically once the round finalizes.

Build Your Own Data Feeds

Now that you understand Web2JSON, you can fetch data from ANY public API! Here are some examples:

Crypto Price Feed

typescript
// Fetch Bitcoin price from CoinGecko
const apiUrl = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd";
const postProcessJq = `{ price: .bitcoin.usd }`;
const abiSignature = `{"components":[{"internalType":"uint256","name":"price","type":"uint256"}],"name":"task","type":"tuple"}`;

// Your contract can now use verified Bitcoin price for DeFi applications!

Weather Data

typescript
// Fetch current temperature for New York
const apiUrl = "https://api.open-meteo.com/v1/forecast?latitude=40.71&longitude=-74.01&current=temperature_2m";
const postProcessJq = `{ temperature: .current.temperature_2m }`;
const abiSignature = `{"components":[{"internalType":"uint256","name":"temperature","type":"uint256"}],"name":"task","type":"tuple"}`;

// Create weather-based insurance or event contracts!

Sports Scores

typescript
// Fetch football match results
const apiUrl = "https://api.football-data.org/v4/matches";
const postProcessJq = `{ totalMatches: .resultSet.count }`;
const abiSignature = `{"components":[{"internalType":"uint256","name":"totalMatches","type":"uint256"}],"name":"task","type":"tuple"}`;

// Build prediction markets or fantasy sports!

Endless Possibilities: Stock prices, election results, random numbers, flight prices, real estate data - any public API can become a trusted data source for your blockchain applications!

Ready to Launch Checklist

1
Cloned flare-hardhat-starter repository
2
Ran npm install successfully
3
Created .env file with all required variables
4
Got testnet FLR from faucet for gas fees
5
Compiled contracts with npx hardhat compile
Ready to run: npx hardhat run scripts/fdcExample/Web2Json.ts --network coston2

Congratulations! You're now a Web2JSON expert! You've learned how to bring real-world data to the blockchain trustlessly. This powerful skill opens up endless possibilities for building applications that interact with the real world while maintaining blockchain security and transparency.