# Proof of Reserves

> Verifying stablecoin reserves with 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/guides/hardhat/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 Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) repository.

## The problem[​](#the-problem "Direct link to 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[​](#smart-contract-architecture "Direct link to 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[​](#stablecoin-contract "Direct link to 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.

contracts/proofOfReserves/Token.sol

```
// SPDX-License-Identifier: MITpragma solidity ^0.8.25;import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";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[​](#tokenstatereader-contract "Direct link to 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.

contracts/proofOfReserves/TokenStateReader.sol

```
// SPDX-License-Identifier: MITpragma solidity ^0.8.25;import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";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[​](#proofofreserves-contract "Direct link to 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`.

contracts/proofOfReserves/ProofOfReserves.sol

```
// SPDX-License-Identifier: MITpragma solidity ^0.8.25;import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";import {IEVMTransaction} from "@flarenetwork/flare-periphery-contracts/coston2/IEVMTransaction.sol";import {IWeb2Json} from "@flarenetwork/flare-periphery-contracts/coston2/IWeb2Json.sol";import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";...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:

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:

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.

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.

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.

contracts/proofOfReserves/ProofOfReserves.sol:ProofOfReserves

```
    function abiSignatureHack(DataTransportObject calldata dto) public pure {}
```

We name the function appropriately.

## Process Overview[​](#process-overview "Direct link to 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[​](#scripts "Direct link to 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.

scripts/proofOfReserves/deployToken.ts

```
import hre, { run } from "hardhat";import { MyStablecoinInstance } from "../../typechain-types";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:

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

scripts/proofOfReserves/deployTokenStateReader.ts

```
import hre, { run } from "hardhat";import { TokenStateReaderInstance } from "../../typechain-types";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:

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

scripts/proofOfReserves/deployProofOfReserves.ts

```
import hre, { run } from "hardhat";import { ProofOfReservesInstance } from "../../typechain-types";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:

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

scripts/proofOfReserves/config/costonToken.ts

```
export const costonTokenAddress = "0x313f3dfBfF769816Fd1dEfc10700750d4F391504";
```

scripts/proofOfReserves/config/coston2Token.ts

```
export const coston2TokenAddress = "0x139a0741D26B724fdE6Ddd5aAc8968C172E3975E";
```

scripts/proofOfReserves/config/costonReader.ts

```
export const costonReaderAddress = "0xF118F45Fdb1a7E3Fa2Da5FFB102c1D3EBa97bEB0";
```

scripts/proofOfReserves/config/coston2Reader.ts

```
export const coston2ReaderAddress =  "0x8d01566b34920E3523958b9649f0646F3F544Bf6";
```

scripts/proofOfReserves/config/proofOfReserves.ts

```
export const proofOfReservesAddress =  "0x1052F13b01EA409Ea739563bb4dd842684BA531e";
```

scripts/proofOfReserves/config/all.ts

```
import { costonTokenAddress } from "./costonToken";import { coston2TokenAddress } from "./coston2Token";import { costonReaderAddress } from "./costonReader";import { coston2ReaderAddress } from "./coston2Reader";import { proofOfReservesAddress } from "./proofOfReserves";import { costonTransaction } from "./costonTransaction";import { coston2Transaction } from "./coston2Transaction";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:

scripts/proofOfReserves/config/costonTransaction.ts

```
export const costonTransaction =  "0xc675cc308982a0300e744c937250fc5882c1eaca8266abc5a4fa5d8fa5319909";
```

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

scripts/proofOfReserves/activateTokenStateReader.ts

```
import hre from "hardhat";import { MyStablecoinInstance } from "../../typechain-types";import { TokenStateReaderInstance } from "../../typechain-types";import { tokenAddresses, readerAddresses } from "./config";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:

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

scripts/proofOfReserves/verifyProofOfReserves.ts

```
import hre from "hardhat";import { ProofOfReservesInstance, IRelayInstance } from "../../typechain-types";import {  prepareAttestationRequestBase,  postRequestToDALayer,  sleep,} from "../utils/fdc";import {  getFdcHub,  getRelay,  getFdcVerification,} from "../utils/getters";import {  getFdcRequestFee,  calculateRoundId,} from "../utils/core";import {  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.

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

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

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.

scripts/proofOfReserves/verifyProofOfReserves.ts

```
...async function prepareAttestationRequests(transactions: AttestationRequest[]) {  console.log("\nPreparing data...\n");  var data: Map<string, string> = 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.

scripts/proofOfReserves/verifyProofOfReserves.ts

```
...async function submitAttestationRequests(data: Map<string, string>) {  console.log("\nSubmitting attestation requests...\n");  const fdcHub = await getFdcHub();  var roundIds: Map<string, number> = 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.

scripts/proofOfReserves/verifyProofOfReserves.ts

```
...async function retrieveDataAndProofs(  data: Map<string, string>,  roundIds: Map<string, number>) {  console.log("\nRetrieving data and proofs...\n");  var proofs: Map<string, any> = 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.

scripts/proofOfReserves/verifyProofOfReserves.ts

```
...async function prepareDataAndProofs(data: Map<string, any>) {  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.

scripts/proofOfReserves/verifyProofOfReserves.ts

```
...async function submitDataAndProofsToProofOfReserves(data: Map<string, any>) {  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:

```
yarn hardhat run scripts/proofOfReserves/verifyProofOfReserves.ts --network coston2
```

The whole script is as follows:

scripts/proofOfReserves/verifyProofOfReserves.ts

```
import hre from "hardhat";import type {  ProofOfReservesInstance,  IRelayInstance,} from "../../typechain-types";import {  prepareAttestationRequestBase,  getFdcHub,  getFdcRequestFee,  getRelay,  calculateRoundId,  postRequestToDALayer,  sleep,} from "../fdcExample/Base";import {  tokenAddresses,  readerAddresses,  proofOfReservesAddress,  transactionHashes,} from "./config";const ProofOfReserves = artifacts.require("ProofOfReserves");const {  VERIFIER_URL_TESTNET,  VERIFIER_API_KEY_TESTNET,  VERIFIER_URL_TESTNET,  COSTON2_DA_LAYER_URL,} = process.env;// yarn hardhat run scripts/proofOfReserves/verifyProofOfReserves.ts --network coston2type AttestationRequest = {  source: string;  sourceIdBase: string;  verifierUrlBase: string;  verifierApiKey: string;  urlTypeBase: string;  data: Record<string, unknown>;};const requests: AttestationRequest[] = [  {    source: "web2json",    sourceIdBase: "PublicWeb2",    verifierUrlBase: VERIFIER_URL_TESTNET,    verifierApiKey: VERIFIER_API_KEY_TESTNET,    urlTypeBase: "",    data: {      apiUrl:        "https://api.htdigitalassets.com/alm-stablecoin-db/metrics/current_reserves_amount",      httpMethod: "GET",      headers: "{}",      queryParams: "{}",      body: "{}",      postProcessJq: `{reserves: .value | gsub(",";"") | sub("\\\\.\\\\d*";"")}`,      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"),    },  },];async function prepareWeb2JsonAttestationRequest(  transaction: AttestationRequest,) {  const attestationTypeBase = "Web2Json";  const requestBody = {    url: transaction.data.apiUrl,    httpMethod: transaction.data.httpMethod,    headers: transaction.data.headers,    queryParams: transaction.data.queryParams,    body: transaction.data.body,    postProcessJq: transaction.data.postProcessJq,    abiSignature: transaction.data.abiSignature,  };  const url = `${transaction.verifierUrlBase}Web2Json/prepareRequest`;  const apiKey = transaction.verifierApiKey;  return await prepareAttestationRequestBase(    url,    apiKey,    attestationTypeBase,    transaction.sourceIdBase,    requestBody,  );}async function prepareTransactionAttestationRequest(  transaction: AttestationRequest,) {  const attestationTypeBase = "EVMTransaction";  const requiredConfirmations = "1";  const provideInput = true;  const listEvents = true;  const logIndices: string[] = [];  const requestBody = {    transactionHash: transaction.data.transactionHash,    requiredConfirmations: requiredConfirmations,    provideInput: provideInput,    listEvents: listEvents,    logIndices: logIndices,  };  const url = `${transaction.verifierUrlBase}verifier/${transaction.urlTypeBase}/EVMTransaction/prepareRequest`;  const apiKey = transaction.verifierApiKey;  return await prepareAttestationRequestBase(    url,    apiKey,    attestationTypeBase,    transaction.sourceIdBase,    requestBody,  );}async function prepareAttestationRequests(transactions: AttestationRequest[]) {  console.log("\nPreparing data...\n");  const data: Map<string, string> = 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;}async function submitAttestationRequests(data: Map<string, string>) {  console.log("\nSubmitting attestation requests...\n");  const fdcHub = await getFdcHub();  const roundIds: Map<string, number> = 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;}async function retrieveDataAndProofs(  data: Map<string, string>,  roundIds: Map<string, number>,) {  console.log("\nRetrieving data and proofs...\n");  const proofs: Map<string, unknown> = 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();    while (!(await relay.isFinalized(200, roundId))) {      await sleep(10000);    }    console.log("Round finalized!\n");    const request = {      votingRoundId: roundId,      requestBytes: data.get(source),    };    console.log("Prepared request:\n", request, "\n");    let 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;}async function prepareDataAndProofs(data: Map<string, unknown>) {  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,    ),  };  const transactionProofs: Array<Record<string, unknown>> = [];  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];}async function submitDataAndProofsToProofOfReserves(  data: Map<string, Record<string, unknown>>,) {  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;}async function main() {  const data = await prepareAttestationRequests(requests);  const roundIds = await submitAttestationRequests(data);  const proofs = await retrieveDataAndProofs(data, roundIds);  const sufficientReserves = await submitDataAndProofsToProofOfReserves(proofs);  console.log("Sufficient reserves:", sufficientReserves);}void main().then(() => {  process.exit(0);});
```
