# Onchain report verification (EVM chains)
Source: https://docs.chain.link/data-streams/reference/data-streams-api/onchain-verification

> For the complete documentation index, see [llms.txt](/llms.txt).

> **CAUTION: Onchain Data Verification**
>
> Onchain verification ensures the integrity of reports by confirming their authenticity as signed by the Decentralized
> Oracle Network (DON). It is the responsibility of the application contract(s) to determine the suitability of the
> report data for any further actions, such as trade execution.

## About Verification

[When you fetch a report](/data-streams/tutorials/go-sdk-fetch) from Data Streams, you receive a [signed payload](/tutorials/go-sdk-fetch#payload-for-onchain-verification). Onchain verification submits that payload to Chainlink's `VerifierProxy` contract, which checks the DON's signature and returns the decoded report data your contract can use. Your contract never talks to the Verifier contract directly: the proxy handles routing.

The `IVerifierProxy` interface exposes two verification functions:

| Function                      | Use case                                                |
| ----------------------------- | ------------------------------------------------------- |
| [`verify()`](#verify)         | Verify a single report payload in one transaction       |
| [`verifyBulk()`](#verifybulk) | Verify multiple report payloads in a single transaction |

**What Chainlink deploys:** The `VerifierProxy` contract. You only need its address, which is listed on the [Stream Addresses](/data-streams/crypto-streams) page.

**What you deploy:** Your own contract that calls `VerifierProxy.verify()` or `verifyBulk()`. The [contract example](#contract-example) below shows the full pattern and is a starting point — see the disclaimer before using it in production.

## IVerifierProxy interface

`IVerifierProxy` is the single onchain entry point for report verification. Your contract calls it directly — you never interact with the underlying Verifier contract. The proxy routes each call to the correct Verifier, handles fee collection through the `FeeManager` (when one is deployed), and returns the verified report data.

You will need the deployed `VerifierProxy` address for the network you are targeting. Find it on the [Stream Addresses](/data-streams/crypto-streams) page.

```solidity
interface IVerifierProxy {
  // Verify a single report. Returns the verified report body as ABI-encoded bytes.
  // Decode the return value into the appropriate report struct (v3, v8, etc.).
  function verify(
    bytes calldata payload,           // Full report payload from the Streams API
    bytes calldata parameterPayload   // abi.encode(feeTokenAddress), or "" if no FeeManager
  ) external payable returns (bytes memory verifierResponse);

  // Verify multiple reports in one transaction. Accepts different feed IDs in the same call.
  // Returns verified report bytes in the same order as the input array.
  function verifyBulk(
    bytes[] calldata payloads,        // Array of report payloads — may span different feed IDs
    bytes calldata parameterPayload   // abi.encode(feeTokenAddress), or "" if no FeeManager
  ) external payable returns (bytes[] memory verifiedReports);

  // Returns the FeeManager address, or the zero address if no FeeManager is deployed on this chain.
  // Use this to determine whether fee quoting and approval steps are required before verifying.
  function s_feeManager() external view returns (IVerifierFeeManager);
}
```

### `verify()`

Verifies a single report payload and optionally collects the verification fee. Pass the raw payload bytes returned by the Streams API directly as `payload` — no transformation is needed.

| Parameter          | Type             | Description                                                                                                         |
| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------- |
| `payload`          | `bytes calldata` | The full report payload returned by the Streams API (header + signed report body).                                  |
| `parameterPayload` | `bytes calldata` | ABI-encoded fee token address (e.g. `abi.encode(linkTokenAddress)`), or empty bytes `""` when no FeeManager exists. |

#### Returns

| Return             | Type           | Description                                                                                                                                                           |
| ------------------ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verifierResponse` | `bytes memory` | ABI-encoded verified report body. Decode into the appropriate report struct according to the [report schema version](/data-streams/reference/report-schema-overview). |

#### Fee handling for `verify()`

Before calling `verify()`, check whether a `FeeManager` is deployed on the target network by calling `s_feeManager()` on the proxy.

- **If `s_feeManager()` returns a non-zero address**: quote the fee using `getFeeAndReward`, approve the `RewardManager` to spend that LINK amount, then pass `abi.encode(feeTokenAddress)` as `parameterPayload`.
- **If `s_feeManager()` returns the zero address**: no fee is required. Pass empty bytes `""` as `parameterPayload`.

Networks with a `FeeManager` currently deployed include: Arbitrum, Avalanche, Base, Blast, Bob, Ink, Linea, OP, Scroll, Soneium, and ZKSync.

### `verifyBulk()`

Verifies multiple report payloads in a single transaction. Reports may reference different feed IDs, enabling atomic multi-feed updates. Pass the raw payload bytes from the Streams API directly — no transformation is needed.

| Parameter          | Type               | Description                                                                                                                                   |
| ------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `payloads`         | `bytes[] calldata` | Array of full report payloads from the Streams API. Each entry may correspond to a different feed ID. Order is preserved in the output.       |
| `parameterPayload` | `bytes calldata`   | ABI-encoded fee token address shared across all reports (e.g. `abi.encode(linkTokenAddress)`), or empty bytes `""` when no FeeManager exists. |

#### Returns

| Return            | Type             | Description                                                                                                                        |
| ----------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `verifiedReports` | `bytes[] memory` | Array of ABI-encoded verified report bodies in the same order as `payloads`. Decode each entry into the appropriate report struct. |

#### Fee handling for `verifyBulk()`

The same `FeeManager` check applies. When a `FeeManager` is deployed, the total fee equals the sum of individual fees across all reports in the batch. Your contract must:

1. Decode each payload and call `getFeeAndReward` once per report to quote its fee.
2. Sum the individual fees to get the total amount required.
3. Approve the `RewardManager` contract to spend the total LINK amount in a single `approve()` call.
4. Pass the ABI-encoded fee token address as `parameterPayload`.

When no `FeeManager` is deployed, pass empty bytes as `parameterPayload` and skip all fee logic.

## When to use `verifyBulk()`

Use `verifyBulk()` when your protocol needs price data from multiple feeds in the same block. Common use cases include:

- Calculating a portfolio's value across multiple asset prices atomically.
- Executing a trade that depends on both a base and a quote asset price (e.g. ETH/USD and BTC/USD).
- Updating multiple onchain price references in a single transaction to reduce state inconsistency.

`verifyBulk()` accepts multiple feed IDs in the same call — there is no requirement that all payloads reference the same stream.

## Gas considerations

`verifyBulk()` reduces overhead compared to issuing N separate `verify()` calls, each of which incurs an additional base transaction cost (21,000 gas). The per-report cryptographic verification cost is the same regardless of which function you use.

- **What you save**: one base transaction cost (21,000 gas) per additional report beyond the first.
- **What you do not save**: the cryptographic verification cost per report.
- **Fee cost**: verification fees (when applicable) are the same per report regardless of whether you use `verify()` or `verifyBulk()`.

## Contract example

The contract below is an **example you deploy yourself**. It demonstrates the full verification pattern for both `verifyReport()` (single report) and `verifyBulkReports()` (multiple reports), including fee handling. Use it as a reference when writing your own contract.

Your contract must have sufficient LINK tokens to pay for verification fees on networks where fees are required. Learn how to [fund your contract with LINK tokens](/resources/fund-your-contract).

<CodeSample src="samples/DataStreams/ClientReportsVerifier.sol" />

### `verifyReport()` — single report

- **Input**: one `bytes` payload from the Streams API — pass it directly.
- **Fee**: checks for a `FeeManager`, quotes the fee if one exists, and approves that exact amount before calling `verify()`.
- **Output**: stores the decoded price in `lastDecodedPrice` and emits `DecodedPrice(int192 price)`.

See the [Verify report data onchain (EVM)](/data-streams/tutorials/evm-onchain-report-verification) tutorial for a step-by-step walkthrough using this function.

### `verifyBulkReports()` — multiple reports

- **Input**: `bytes[]` array of payloads from the Streams API — each may reference a different feed ID.
- **Fee**: calls `getFeeAndReward` once per report, sums the results, and issues a single `approve()` for the combined total before calling `verifyBulk()`.
- **Output**: stores the decoded prices in `lastDecodedPrices` (an `int192[]` array in the same order as the inputs) and emits `DecodedPrice(int192 price)` once per report.