Using the StreamsLookup error handler
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
.
Error handler
When Automation detects an event, it runs the checkLog
function, which includes a StreamsLookup revert custom error. The StreamsLookup revert enables your upkeep to fetch a report from Data Streams. If reports are fetched successfully, the checkCallback
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 also shows each function outlined in the diagram below:

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.
- Add the
checkErrorHandler
function in your contract to specify how you want to handle error codes. For example, you could decide to ignore any codes related to bad requests or incorrect input, without runningperformUpkeep
onchain:
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 success = false;
bool isError = true;
if (errorCode == 800400) {
// Handle bad request error code offchain
_upkeepNeeded = false;
} else {
// logic to handle other errors
}
return (_upkeepNeeded, abi.encode(isError, abi.encode(errorCode, extraData, success)));
}
- Define custom logic for the alternative path within
performUpkeep
, to handle any error codes you did not intercept offchain incheckErrorHandler
:
// function will be performed on-chain
function performUpkeep(bytes calldata performData) external {
// Decode incoming performData
(bool isError, bytes payload) = abi.decode(performData)
// Unpacking the errorCode from checkErrorHandler
if (isError){
(uint errorCode, bytes memory extraData, bool reportSuccess) = abi.decode(
payload,
(uint, bytes, bool)
// Define custom logic here to handle error codes onchain
);
} else {
// Otherwise unpacking info from checkCallback
(bytes[] memory signedReports, bytes memory extraData, bool reportSuccess) = abi.decode(
payload,
(bytes[], bytes, bool)
);
if (reportSuccess) {
bytes memory report = signedReports[0];
(, bytes memory reportData) = abi.decode(report, (bytes32[3], bytes));
// Logic to verify and decode report
} else {
// Logic in case reports were not pulled successfully
}
}
}
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:
- Specifying an incorrect
feedID
to force error code 800400 (ErrCodeStreamsBadRequest
) - Specifying a future timestamp to force error code 808206 (where partial content is received) for both single
feedID
and bulkfeedID
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 request
If your StreamsLookup revert function is defined incorrectly in your smart contracts, the nodes will not be able to decode it.
Error codes
Error code | Retries | Possible cause of error | Data Streams equivalent API response |
---|---|---|---|
No error | N/A | No error | 200 (Status OK) |
ErrCodeStreamsBadRequest: 800400 | No | User requested 0 feeds | N/A |
User error, incorrect parameter input | 400 (StatusBadRequest) | ||
Issue with encoding http url (bad characters) | N/A | ||
ErrCodeStreamsUnauthorized: 808401 | No | Key access issue or incorrect feedID | 401 (StatusUnauthorized) |
808206 | Log trigger - after retries; Conditional immediately | Requested m reports but only received n (partial) | 200 (Status OK) or StatusPartialContent |
8085XX (e.g 808500) | Log trigger - after retries; Conditional immediately | No response |
|
ErrCodeStreamsBadResponse: 808600 | No | Error in reading body of returned response, but service is up | 200 but can't decode Data Streams output |
ErrCodeStreamsTimeout: 808601 | No | No valid report is received for 10 seconds (e.g. Data Streams returns 200 but no report) | No valid Data Streams report received within 10 seconds (200 but no report) |
ErrCodeStreamsUnknownError: 808700 | No | Unknown | Anything else |
Example code
This example code highlights the revert StreamsLookup
, checkCallback
, checkErrorHandler
and performUpkeep
functions. The full code example is available here.
...
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,
""
);
}
function checkCallback(bytes[] calldata values, bytes calldata extraData)
external
pure
returns (bool, bytes memory)
{
bool success = true;
return (true, abi.encode(values, extraData, success));
}
function checkErrorHandler(
bytes calldata errorCode,
bytes calldata extraData
) public view returns (bool upkeepNeeded, bytes memory performData) {
bool success = false;
return (true, abi.encode(errorCode, extraData, success));
}
// function will be performed on-chain
function performUpkeep(bytes calldata performData) external {
// Decode incoming performData
(bool isError, bytes payload) = abi.decode(performData)
// Unpacking the errorCode from checkErrorHandler
if (isError){
(uint errorCode, bytes memory extraData, bool reportSuccess) = abi.decode(
payload,
(uint, bytes, bool)
);
} else {
// Otherwise unpacking info from checkCallback
(bytes[] memory signedReports, bytes memory extraData, bool reportSuccess) = abi.decode(
payload,
(bytes[], bytes, bool)
);
}
if (reportSuccess) {
bytes memory report = signedReports[0];
(, bytes memory reportData) = abi.decode(
report,
(bytes32[3], bytes)
);
// Logic to verify and decode report
} else {
// Custom logic to handle error codes
}
}