# Using the StreamsLookup error handler
Source: https://docs.chain.link/chainlink-automation/guides/streams-lookup-error-handler

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

> **NOTE: Talk to an expert**
>
> [Contact us](https://chain.link/contact?ref_id=datastreams) to talk to an expert about integrating Chainlink Data
> Streams with your applications.The Chainlink Automation StreamsLookup error handler provides insight into potential errors or edge cases in StreamsLookup upkeeps. The table below outlines a range of error codes and the behavior associated with the codes. Use the `checkErrorHandler` function to specify how you want to respond to the error codes. `checkErrorHandler` is simulated offchain and determines what action for Automation to take onchain in `performUpkeep`.> **CAUTION: Developer responsibility**
>
> Developers implementing Chainlink products are solely responsible for maintaining the security and user experience of
> their applications. Developers must monitor and mitigate any potential application code risks that may, among other
> things, result in unanticipated application behavior, including by instituting requisite [risk mitigation
> processes](/data-feeds/selecting-data-feeds#risk-mitigation) including, but not limited to, data quality checks,
> circuit breakers, and appropriate contingency logic for their use case.## Error handlerWhen Automation detects an event, it runs the `checkLog` function, which includes a [StreamsLookup revert](/chainlink-automation/reference/automation-interfaces#streamslookup-revert) custom error. The StreamsLookup revert enables your upkeep to fetch a report from Data Streams. If reports are fetched successfully, the [`checkCallback`](/chainlink-automation/reference/automation-interfaces#checkcallback-function) function is evaluated offchain. Otherwise, the `checkErrorHandler` function is evaluated offchain to determine what Automation should do next. Both of these functions have the same output types (`bool upkeepNeeded, bytes memory performData`), which Automation uses to run `performUpkeep` onchain. The [example code](#example-code) also shows each function outlined in the diagram below:![Error handler flow diagram](/images/automation/streamslookup-errorhandler-horizontal.png)If the Automation network fails to get the requested reports, an error code is sent to the `checkErrorHandler` function in your contract. If your contract doesn't have the `checkErrorHandler` function, nothing will happen. If your contract has the `checkErrorHandler` function, it is evaluated offchain to determine what to do next. For example, you could intercept or ignore certain errors and decide not to run `performUpkeep` in those cases, in order to save time and gas. For other errors, you can execute an alternative path within `performUpkeep`, and the upkeep runs the custom logic you define in your `performUpkeep` function to handle those errors.1) Add the `checkErrorHandler` function in your contract to specify how you want to handle [error codes](#error-codes). For example, you could decide to ignore any codes related to bad requests or incorrect input, without running `performUpkeep` onchain:

   ```solidity
   /**
    * @notice Determines the need for upkeep in response to an error from Data Streams.
    * @param errorCode The error code returned by the Data Streams lookup.
    * @param extraData Additional context or data related to the error condition.
    * @return upkeepNeeded Boolean indicating whether upkeep is needed based on the error.
    * @return performData Data to be used if upkeep is performed, encoded with success state and error context.
    */
   function checkErrorHandler(
     uint errorCode,
     bytes calldata extraData
   ) external returns (bool upkeepNeeded, bytes memory performData) {
     // Add custom logic to handle errors offchain here
     bool _upkeepNeeded = true;
     bool reportSuccess = false;
     if (errorCode == 808400) {
       // Handle bad request errors code offchain.
       // In this example, no upkeep needed for bad request errors.
       _upkeepNeeded = false;
     } else {
       // Handle other errors as needed.
     }
     return (_upkeepNeeded, abi.encode(reportSuccess, abi.encode(errorCode, extraData)));
   }
   ```

2) Define custom logic for the alternative path within `performUpkeep`, to handle any error codes you did not intercept offchain in `checkErrorHandler`:

   ```solidity
   // function will be performed on-chain
   function performUpkeep(bytes calldata performData) external {
     // Decode incoming performData
     (bool reportSuccess, bytes memory payload) = abi.decode(performData, (bool, bytes));

     if (reportSuccess) {
       // Decode the performData bytes passed in by CL Automation.
       // This contains the data returned by your implementation in checkCallback().
       (bytes[] memory signedReports, bytes memory extraData) = abi.decode(payload, (bytes[], bytes));
       // Logic to verify and decode report
       // ...
     } else {
       // Handle error condition
       (uint errorCode, bytes memory extraData) = abi.decode(payload, (uint, bytes));
       // Custom logic to handle error codes
     }
   }
   ```### Testing checkErrorHandler`checkErrorHandler` is simulated offchain. When `upkeepNeeded` returns `true`, Automation runs `performUpkeep` onchain using the `performData` from `checkErrorHandler`. If the `checkErrorHandler` function itself reverts, `performUpkeep` does not run.If you need to force errors in StreamsLookup while testing, you can try the following methods:* Not specifying any `feedID` to force error code 808400 (`ErrCodeStreamsBadRequest`)
* Specifying an incorrect `feedID` to force error code 808401 (`ErrCodeStreamsBadRequest`)
* Specifying a future timestamp to force error code 808206 (where partial content is received) for both single `feedID` and bulk `feedID` requests
* Specifying old timestamps for reports not available anymore yields either error code 808504 (no response) or 808600 (bad response), depending on which service calls the timeout requestIf your [StreamsLookup revert](/chainlink-automation/reference/automation-interfaces#streamslookup-revert) function is defined incorrectly in your smart contracts, the nodes will not be able to decode it.## Error codes## Example codeThis example code includes the `revert StreamsLookup`, `checkCallback`, `checkErrorHandler` and `performUpkeep` functions. The full code example is available [here](https://github.com/smartcontractkit/documentation/blob/main/public/samples/DataStreams/StreamsUpkeepWithErrorHandler.sol).```sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ILogAutomation, Log} from "@chainlink/contracts/src/v0.8/automation/interfaces/ILogAutomation.sol";
import {
  StreamsLookupCompatibleInterface
} from "@chainlink/contracts/src/v0.8/automation/interfaces/StreamsLookupCompatibleInterface.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

// =====================
// INTERFACES
// =====================

interface IVerifierProxy {
  /**
   * @notice Verifies that the data encoded has been signed.
   * correctly by routing to the correct verifier.
   * @param payload The encoded data to be verified, including the signed
   * report.
   * @param parameterPayload Empty bytes for Data Streams subscription billing.
   * @return verifierResponse The encoded report from the verifier.
   */
  function verify(
    bytes calldata payload,
    bytes calldata parameterPayload
  ) external payable returns (bytes memory verifierResponse);
}

// ==========================
// CONTRACT IMPLEMENTATION
// ==========================

contract StreamsUpkeepWithErrorHandler is ILogAutomation, StreamsLookupCompatibleInterface {
  error InvalidReportVersion(uint16 version); // Thrown when an unsupported report version is provided to verifyReport.

  /**
   * @dev Represents a data report from a Data Streams feed for v3 schema (crypto streams).
   * The `price`, `bid`, and `ask` values are carried to either 8 or 18 decimal places, depending on the feed.
   * For more information, see https://docs.chain.link/data-streams/crypto-streams and
   * https://docs.chain.link/data-streams/reference/report-schema
   */
  struct ReportV3 {
    bytes32 feedId; // The feed ID the report has data for.
    uint32 validFromTimestamp; // Earliest timestamp for which price is applicable.
    uint32 observationsTimestamp; // Latest timestamp for which price is applicable.
    uint192 nativeFee; // Legacy onchain verification fee field.
    uint192 linkFee; // Legacy onchain verification fee field. Not used for subscription billing.
    uint32 expiresAt; // Latest timestamp where the report can be verified onchain.
    int192 price; // DON consensus median price (8 or 18 decimals).
    int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation (8 or 18 decimals).
    int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation (8 or 18
    // decimals).
  }

  /**
   * @dev Represents a data report from a Data Streams feed for v4 schema (RWA feeds).
   * The `price` value is carried to either 8 or 18 decimal places, depending on the feed.
   * The `marketStatus` indicates whether the market is currently open. Possible values: `0` (`Unknown`), `1`
   * (`Closed`), `2` (`Open`).
   * For more information, see https://docs.chain.link/data-streams/rwa-streams and
   * https://docs.chain.link/data-streams/reference/report-schema-v4
   */
  struct ReportV4 {
    bytes32 feedId; // The feed ID the report has data for.
    uint32 validFromTimestamp; // Earliest timestamp for which price is applicable.
    uint32 observationsTimestamp; // Latest timestamp for which price is applicable.
    uint192 nativeFee; // Legacy onchain verification fee field.
    uint192 linkFee; // Legacy onchain verification fee field. Not used for subscription billing.
    uint32 expiresAt; // Latest timestamp where the report can be verified onchain.
    int192 price; // DON consensus median benchmark price (8 or 18 decimals).
    uint32 marketStatus; // The DON's consensus on whether the market is currently open.
  }

  struct Quote {
    address quoteAddress;
  }

  event PriceUpdate(int192 indexed price);
  event ErrorTestLog(uint256 indexed errorCode);

  IVerifierProxy public verifier;

  string public constant STRING_DATASTREAMS_FEEDLABEL = "feedIDs";
  string public constant STRING_DATASTREAMS_QUERYLABEL = "timestamp";
  uint256 public s_error;
  bool public s_isError;
  string[] public feedIds = [
    "0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782" // Ex. ETH/USD Feed ID
  ];

  constructor(
    address _verifier
  ) {
    verifier = IVerifierProxy(_verifier); //Arbitrum Sepolia: 0x2ff010debc1297f19579b4246cad07bd24f2488a
  }

  // This function uses revert to convey call information.
  // See https://eips.ethereum.org/EIPS/eip-3668#rationale for details.
  function checkLog(
    Log calldata log,
    bytes memory
  ) external returns (bool upkeepNeeded, bytes memory performData) {
    revert StreamsLookup(STRING_DATASTREAMS_FEEDLABEL, feedIds, STRING_DATASTREAMS_QUERYLABEL, log.timestamp, "");
  }

  /**
   * @dev This function is intended for off-chain simulation by Chainlink Automation to pass in the data reports fetched
   * from Data Streams.
   * @param values The bytes array of data reports fetched from Data Streams.
   * @param extraData Contextual or additional data related to the feed lookup process.
   * @return upkeepNeeded Indicates that upkeep is needed to pass the data to the on-chain performUpkeep function.
   * @return performData Encoded data indicating success and including the original `values` and `extraData`, to be used
   * in `performUpkeep`.
   */
  function checkCallback(
    bytes[] calldata values,
    bytes calldata extraData
  ) external pure returns (bool upkeepNeeded, bytes memory) {
    bool reportSuccess = true; // Indicates successful data retrieval
    return (true, abi.encode(reportSuccess, abi.encode(values, extraData)));
  }

  /**
   * @notice Determines the need for upkeep in response to an error from Data Streams.
   * @dev This function serves as an example of how errors can be handled offchain.
   * @dev Developers can parameterize this logic as needed.
   * @dev All error codes are documented at:
   * https://docs.chain.link/chainlink-automation/guides/streams-lookup-error-handler#error-codes
   * @param errorCode The error code returned by the Data Streams lookup.
   * @param extraData Additional context or data related to the error condition.
   * @return upkeepNeeded Boolean indicating whether upkeep is needed based on the error.
   * @return performData Data to be used if upkeep is performed, encoded with success state and error context.
   */
  function checkErrorHandler(
    uint256 errorCode,
    bytes calldata extraData
  ) external view returns (bool upkeepNeeded, bytes memory performData) {
    bool _upkeepNeeded = false;
    bool reportSuccess = false;
    if (errorCode == 0) {
      // If there is no error, proceed with the performUpkeep and
      // the report decoding/verification
      _upkeepNeeded = true;
      reportSuccess = true;
    } else if (errorCode == 808_400 || errorCode == 808_401) {
      // Mark upkeep as needed for bad requests (808400) and incorrect feed ID (808401)
      // to handle these specific errors onchain.
      _upkeepNeeded = true;
      // Note that reportSuccess remains false.
    } else {
      // For other error codes, decide not to perform upkeep.
      // This is the default behavior, explicitly noted for clarity in this example.
      _upkeepNeeded = false;
      reportSuccess = false;
    }
    return (_upkeepNeeded, abi.encode(reportSuccess, abi.encode(errorCode, extraData)));
  }

  function performUpkeep(
    bytes calldata performData
  ) external {
    // Decode incoming performData
    (bool reportSuccess, bytes memory payload) = abi.decode(performData, (bool, bytes));

    if (reportSuccess) {
      // Decode the performData bytes passed in by CL Automation.
      // This contains the data returned by your implementation in checkCallback().
      (bytes[] memory signedReports, bytes memory extraData) = abi.decode(payload, (bytes[], bytes));
      // Logic to verify and decode report
      bytes memory unverifiedReport = signedReports[0];

      (, bytes memory reportData) = abi.decode(unverifiedReport, (bytes32[3], bytes));

      // Extract report version from reportData
      uint16 reportVersion = (uint16(uint8(reportData[0])) << 8) | uint16(uint8(reportData[1]));

      // Validate report version
      if (reportVersion != 3 && reportVersion != 4) {
        revert InvalidReportVersion(uint8(reportVersion));
      }

      // Verify the report. Data Streams uses subscription billing, so no fee metadata is required.
      bytes memory verifiedReportData = verifier.verify(unverifiedReport, bytes(""));

      // Decode verified report data into the appropriate Report struct based on reportVersion
      if (reportVersion == 3) {
        // v3 report schema
        ReportV3 memory verifiedReport = abi.decode(verifiedReportData, (ReportV3));

        // Log price from report
        emit PriceUpdate(verifiedReport.price);
      } else if (reportVersion == 4) {
        // v4 report schema
        ReportV4 memory verifiedReport = abi.decode(verifiedReportData, (ReportV4));

        // Log price from report
        emit PriceUpdate(verifiedReport.price);
      }
    } else {
      // Handle error condition
      (uint256 errorCode, bytes memory extraData) = abi.decode(payload, (uint256, bytes));
      // Custom logic to handle error codes
      s_error = errorCode;
      s_isError = true;
    }
  }

  fallback() external payable {}

  receive() external payable {}
}
```