Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions data-streams/getting-started/hardhat/.gitignore
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
node_modules
# Node modules
/node_modules

# Environment variables
.env
.env.enc

# Compilation output
/dist

# pnpm deploy output
/bundle

# Hardhat Build Artifacts
/artifacts
/build

# Hardhat files
# Hardhat compilation (v2) support directory
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types
# Typechain output
/types

# solidity-coverage files
# Hardhat coverage reports
/coverage
/coverage.json

# Hardhat Ignition deployments
/ignition/deployments
3 changes: 0 additions & 3 deletions data-streams/getting-started/hardhat/.npmignore

This file was deleted.

11 changes: 0 additions & 11 deletions data-streams/getting-started/hardhat/.prettierignore

This file was deleted.

21 changes: 0 additions & 21 deletions data-streams/getting-started/hardhat/.prettierrc

This file was deleted.

7 changes: 0 additions & 7 deletions data-streams/getting-started/hardhat/.solhint.json

This file was deleted.

1 change: 0 additions & 1 deletion data-streams/getting-started/hardhat/.solhintignore

This file was deleted.

40 changes: 25 additions & 15 deletions data-streams/getting-started/hardhat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ This guide shows you how to read data from a Data Streams feed, verify the answe
- Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Engine, return the data in a callback, and run the [`performUpkeep` function](https://docs.chain.link/chainlink-automation/reference/automation-interfaces#performupkeep-function-for-log-triggers) on your registered upkeep contract.
- The `performUpkeep` function calls the `verify` function on the verifier contract.

> :warning: **Disclaimer**: "This tutorial represents an educational example to use a Chainlink system, product, or service and is provided to demonstrate how to interact with Chainlinks systems, products, and services to integrate them into your own. This template is provided AS IS and AS AVAILABLE without warranties of any kind, it has not been audited, and it may be missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the code in this example in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code."
> :warning: **Disclaimer**: "This tutorial represents an educational example to use a Chainlink system, product, or service and is provided to demonstrate how to interact with Chainlink's systems, products, and services to integrate them into your own. This template is provided "AS IS" and "AS AVAILABLE" without warranties of any kind, it has not been audited, and it may be missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the code in this example in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code."

## Before you begin

This guide uses the [Hardhat](https://hardhat.org/) development environment to deploy and interact with the contracts. To learn more about Hardhat, read the [Hardhat Documentation](https://hardhat.org/hardhat-runner/docs/getting-started).

### Requirements

- **Git**: Make sure you have Git installed. You can check your current version by running <CopyText text="git --version" code/> in your terminal and download the latest version from the official [Git website](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) if necessary.
- **Nodejs** and **npm**: [Install the latest release of Node.js 20](https://nodejs.org/en/download/). Optionally, you can use the nvm package to switch between Node.js versions with <CopyText text="nvm use 20" code/>. To ensure you are running the correct version in a terminal, type <CopyText text="node -v" code/>.
- **Git**: Make sure you have Git installed. You can check your current version by running `git --version` in your terminal and download the latest version from the official [Git website](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) if necessary.
- **Node.js** and **npm**: [Install the latest release of Node.js 22](https://nodejs.org/en/download/). Optionally, you can use the nvm package to switch between Node.js versions with `nvm use 22`. To ensure you are running the correct version in a terminal, type `node -v`.
```bash
$ node -v
v20.11.0
v22.x.x
```
- **RPC URL**: You need a Remote Procedure Call (RPC) URL for the Arbitrum Sepolia network. You can obtain one by creating an account on [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/) and setting up an Arbitrum Sepolia project.
- **Private key**: You need the private key of the account that will deploy and interact with the contracts. You can use the private key of your [MetaMask wallet](https://metamask.io/).
Expand Down Expand Up @@ -63,17 +63,20 @@ Deploy an upkeep contract that is enabled to retrieve data from Data Streams. Fo
Execute the following command to deploy the Chainlink Automation upkeep contract and the Log Emitter contract to the Arbitrum Sepolia network.

```bash
npx hardhat deployAll --network arbitrumSepolia
npx hardhat ignition deploy ignition/modules/StreamsModule.ts --network arbitrumSepolia
```

Expect output similar to the following in your terminal:

```bash
ℹ Deploying StreamsUpkeepRegistrar contract...
✔ StreamsUpkeepRegistrar deployed at: 0x48403478Aa021A9BC30Da0BDE47cbc155CcA8916
ℹ Deploying LogEmitter contract...
✔ LogEmitter deployed at: 0xD721337a827F9D814daEcCc3c7e72300af914BFE
✔ All contracts deployed successfully.
Deploying [ StreamsModule ]
Batch #1
Executed StreamsModule#LogEmitter
Executed StreamsModule#StreamsUpkeepRegistrar
[ StreamsModule ] successfully deployed 🚀
Deployed Addresses
StreamsModule#LogEmitter - 0x9B68AB315EA2DBD4Ce61e31Dc2c784101338bAaA
StreamsModule#StreamsUpkeepRegistrar - 0x07E8dEe8Ce82Ba750E525EB9151b57a76f731806
```

Save the deployed contract addresses for both contracts. You will use these addresses later.
Expand All @@ -83,18 +86,22 @@ Save the deployed contract addresses for both contracts. You will use these addr
In this example, the upkeep contract pays for onchain verification of reports from Data Streams. The Automation subscription does not cover the cost. Transfer `1.5` testnet LINK to the upkeep contract address you saved earlier. You can retrieve unused LINK later.

```bash
npx hardhat transfer-link --recipient <StreamsUpkeepRegistrarAddress> --amount 1500000000000000000 --network arbitrumSepolia
npx hardhat transfer-link --recipient <StreamsUpkeepRegistrarAddress> --amount 1.5 --network arbitrumSepolia
```

Replace `<StreamsUpkeepRegistrarAddress>` with the address of the `StreamsUpkeepRegistrar` contract you saved earlier.

Expect output similar to the following in your terminal:

```bash
ℹ Starting LINK transfer from <YOUR_ADDRESS> to the streams upkeep contract at 0xD721337a827F9D814daEcCc3c7e72300af914BFE
ℹ Starting LINK transfer from <YOUR ADDRESS> to 0x07E8dEe8Ce82Ba750E525EB9151b57a76f731806
ℹ LINK token address: 0xb1D4538B4571d411F07960EF2838Ce337FE1E80E
ℹ LINK balance of sender 0x45C90FBb5acC1a5c156a401B56Fea55e69E7669d is 6.5 LINK
✔ 1.5 LINK were sent from 0x45C90FBb5acC1a5c156a401B56Fea55e69E7669d to 0xD721337a827F9D814daEcCc3c7e72300af914BFE. Transaction Hash: 0xf241bf4415ec081325ccd8ec3d54432e424afd16f1c81fa78b291ae9a0c03ce2
ℹ LINK balance of sender <YOUR ADDRESS> is 38.84611618077145972 LINK
ℹ Transaction submitted: 0xf170c98ec99b322747ec3b87306f37f384c978b84cee11006d9b80d9cee6e78d
✔ 1.5 LINK were sent from <YOUR ADDRESS> to 0x07E8dEe8Ce82Ba750E525EB9151b57a76f731806
Transaction Hash: 0xf170c98ec99b322747ec3b87306f37f384c978b84cee11006d9b80d9cee6e78d
Block Number: 207017202
Gas Used: 51658
```

### Register and fund the upkeep
Expand All @@ -107,6 +114,9 @@ npx hardhat registerAndFundUpkeep --streams-upkeep <StreamsUpkeepRegistrarAddres

Replace `<StreamsUpkeepRegistrarAddress>` and `<LogEmitterAddress>` with the addresses of your `StreamsUpkeepRegistrar` and `LogEmitter` contracts.

> **Note:** This step may fail. Chainlink Automation auto-approval is currently disabled on several chains, which [requires approval from the Chainlink Automation team](https://chainlinkcommunity.typeform.com/to/m10dC36d).
> You may use https://automation.chain.link/ to register your Upkeep contract and log emitter. Depending on your network, you may be asked to submit verification prior to registration.

Expect output similar to the following in your terminal:

```bash
Expand All @@ -133,7 +143,7 @@ After the transaction is complete, the log is emitted, and the upkeep is trigger

### View the retrieved price

The retrieved price is stored in the `s_last_retrieved_price` contract variable and emitted in the logs. To see the price retrieved by the `StreamsUpkeepRegistrar` contract:
The retrieved price is stored in the `lastDecodedPrice` contract variable and emitted in the logs. To see the price retrieved by the `StreamsUpkeepRegistrar` contract:

```bash
npx hardhat getLastRetrievedPrice --streams-upkeep <StreamsUpkeepRegistrarAddress> --network arbitrumSepolia
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity ^0.8.20;

/**
* @title Log Emitter Contract
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity ^0.8.20;

import {Common} from "@chainlink/contracts/src/v0.8/llo-feeds/libraries/Common.sol";
import {StreamsLookupCompatibleInterface} from
"@chainlink/contracts/src/v0.8/automation/interfaces/StreamsLookupCompatibleInterface.sol";
import {StreamsLookupCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/interfaces/StreamsLookupCompatibleInterface.sol";
import {ILogAutomation, Log} from "@chainlink/contracts/src/v0.8/automation/interfaces/ILogAutomation.sol";
import {IRewardManager} from "@chainlink/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol";
import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol";
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";

/**
Expand Down Expand Up @@ -50,23 +49,27 @@ interface AutomationRegistrarInterface {
* @param requestParams The parameters required for the upkeep registration, encapsulated in `RegistrationParams`.
* @return upkeepID The unique identifier for the registered upkeep, used for future interactions.
*/
function registerUpkeep(RegistrationParams calldata requestParams) external returns (uint256);
function registerUpkeep(
RegistrationParams calldata requestParams
) external returns (uint256);
}

// Custom interfaces for Data Streams: IVerifierProxy and IFeeManager
interface IVerifierProxy {
function verify(bytes calldata payload, bytes calldata parameterPayload)
external
payable
returns (bytes memory verifierResponse);
function verify(
bytes calldata payload,
bytes calldata parameterPayload
) external payable returns (bytes memory verifierResponse);

function s_feeManager() external view returns (IVerifierFeeManager);
}

interface IFeeManager {
function getFeeAndReward(address subscriber, bytes memory unverifiedReport, address quoteAddress)
external
returns (Common.Asset memory, Common.Asset memory, uint256);
function getFeeAndReward(
address subscriber,
bytes memory unverifiedReport,
address quoteAddress
) external returns (Common.Asset memory, Common.Asset memory, uint256);

function i_linkAddress() external view returns (address);

Expand All @@ -75,7 +78,10 @@ interface IFeeManager {
function i_rewardManager() external view returns (address);
}

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

LinkTokenInterface public immutable i_link;
Expand Down Expand Up @@ -168,11 +174,10 @@ contract StreamsUpkeepRegistrar is ILogAutomation, StreamsLookupCompatibleInterf
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try `abi.encode`.
*/
function checkErrorHandler(uint256, /*errCode*/ bytes memory /*extraData*/ )
external
pure
returns (bool upkeepNeeded, bytes memory performData)
{
function checkErrorHandler(
uint256,
/*errCode*/ bytes memory /*extraData*/
) external pure returns (bool upkeepNeeded, bytes memory performData) {
return (true, "0");
// Hardcoded to always perform upkeep.
// Read the StreamsLookup error handler guide for more information.
Expand All @@ -181,36 +186,50 @@ contract StreamsUpkeepRegistrar is ILogAutomation, StreamsLookupCompatibleInterf

// 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(DATASTREAMS_FEEDLABEL, feedIds, DATASTREAMS_QUERYLABEL, log.timestamp, "");
function checkLog(
Log calldata log,
bytes memory
)
external
view
returns (bool /* upkeepNeeded */, bytes memory /* performData */)
{
revert StreamsLookup(
DATASTREAMS_FEEDLABEL,
feedIds,
DATASTREAMS_QUERYLABEL,
log.timestamp,
""
);
}

// The Data Streams report bytes is passed here.
// extraData is context data from feed lookup process.
// Your contract may include logic to further process this data.
// This method is intended only to be simulated offchain by Automation.
// The data returned will then be passed by Automation into performUpkeep
function checkCallback(bytes[] calldata values, bytes calldata extraData)
external
pure
returns (bool, bytes memory)
{
function checkCallback(
bytes[] calldata values,
bytes calldata extraData
) external pure returns (bool, bytes memory) {
return (true, abi.encode(values, extraData));
}

// function will be performed onchain
function performUpkeep(bytes calldata performData) external {
// 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(performData, (bytes[], bytes));
(bytes[] memory signedReports /* bytes memory extraData */, ) = abi
.decode(performData, (bytes[], bytes));

bytes memory unverifiedReport = signedReports[0];

(, /* bytes32[3] reportContextData */ bytes memory reportData) =
abi.decode(unverifiedReport, (bytes32[3], bytes));
(, /* bytes32[3] reportContextData */ 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]));
uint16 reportVersion = (uint16(uint8(reportData[0])) << 8) |
uint16(uint8(reportData[1]));

// Validate report version
if (reportVersion != 3 && reportVersion != 4) {
Expand All @@ -219,27 +238,42 @@ contract StreamsUpkeepRegistrar is ILogAutomation, StreamsLookupCompatibleInterf

// Report verification fees
IFeeManager feeManager = IFeeManager(address(verifier.s_feeManager()));
IRewardManager rewardManager = IRewardManager(address(feeManager.i_rewardManager()));
IRewardManager rewardManager = IRewardManager(
address(feeManager.i_rewardManager())
);

address feeTokenAddress = feeManager.i_linkAddress();
(Common.Asset memory fee,,) = feeManager.getFeeAndReward(address(this), reportData, feeTokenAddress);
(Common.Asset memory fee, , ) = feeManager.getFeeAndReward(
address(this),
reportData,
feeTokenAddress
);

// Approve rewardManager to spend this contract's balance in fees
IERC20(feeTokenAddress).approve(address(rewardManager), fee.amount);

// Verify the report
bytes memory verifiedReportData = verifier.verify(unverifiedReport, abi.encode(feeTokenAddress));
bytes memory verifiedReportData = verifier.verify(
unverifiedReport,
abi.encode(feeTokenAddress)
);

// 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));
ReportV3 memory verifiedReport = abi.decode(
verifiedReportData,
(ReportV3)
);

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

// Store the price from the report
lastDecodedPrice = verifiedReport.price;
Expand Down
Loading
Loading