diff --git a/public/images/data-streams/data-streams-on-demand-offchain-workflows-v3.webp b/public/images/data-streams/data-streams-on-demand-offchain-workflows-v3.webp new file mode 100644 index 00000000000..4043c61b263 Binary files /dev/null and b/public/images/data-streams/data-streams-on-demand-offchain-workflows-v3.webp differ diff --git a/public/images/data-streams/data-streams-trade-architecture-v2.webp b/public/images/data-streams/data-streams-trade-architecture-v2.webp new file mode 100644 index 00000000000..2919eb9a1bb Binary files /dev/null and b/public/images/data-streams/data-streams-trade-architecture-v2.webp differ diff --git a/public/images/data-streams/onchain-verification/deploy-clientReportsVerifier.webp b/public/images/data-streams/onchain-verification/deploy-clientReportsVerifier.webp new file mode 100644 index 00000000000..ac233d41377 Binary files /dev/null and b/public/images/data-streams/onchain-verification/deploy-clientReportsVerifier.webp differ diff --git a/public/images/data-streams/onchain-verification/deployed-clientReportsVerifier-1.webp b/public/images/data-streams/onchain-verification/deployed-clientReportsVerifier-1.webp new file mode 100644 index 00000000000..403ed7e9716 Binary files /dev/null and b/public/images/data-streams/onchain-verification/deployed-clientReportsVerifier-1.webp differ diff --git a/public/images/data-streams/onchain-verification/deployed-clientReportsVerifier-2.webp b/public/images/data-streams/onchain-verification/deployed-clientReportsVerifier-2.webp new file mode 100644 index 00000000000..b7ff2072ab4 Binary files /dev/null and b/public/images/data-streams/onchain-verification/deployed-clientReportsVerifier-2.webp differ diff --git a/public/images/data-streams/onchain-verification/injected-provider.webp b/public/images/data-streams/onchain-verification/injected-provider.webp new file mode 100644 index 00000000000..c301420c2ea Binary files /dev/null and b/public/images/data-streams/onchain-verification/injected-provider.webp differ diff --git a/public/images/data-streams/onchain-verification/price-from-verified-report.webp b/public/images/data-streams/onchain-verification/price-from-verified-report.webp new file mode 100644 index 00000000000..394c533310f Binary files /dev/null and b/public/images/data-streams/onchain-verification/price-from-verified-report.webp differ diff --git a/public/images/data-streams/onchain-verification/solidity-compiler.webp b/public/images/data-streams/onchain-verification/solidity-compiler.webp new file mode 100644 index 00000000000..8aeeae5bd2e Binary files /dev/null and b/public/images/data-streams/onchain-verification/solidity-compiler.webp differ diff --git a/public/images/data-streams/push-based-vs-pull-based-oracles.webp b/public/images/data-streams/push-based-vs-pull-based-oracles.webp index 54ed799f2df..c6420ba65d6 100644 Binary files a/public/images/data-streams/push-based-vs-pull-based-oracles.webp and b/public/images/data-streams/push-based-vs-pull-based-oracles.webp differ diff --git a/public/images/data-streams/streams-direct-offchain-price-updates-v3.webp b/public/images/data-streams/streams-direct-offchain-price-updates-v3.webp new file mode 100644 index 00000000000..66865375ebb Binary files /dev/null and b/public/images/data-streams/streams-direct-offchain-price-updates-v3.webp differ diff --git a/public/images/data-streams/streams-trade-sequence-diagram.webp b/public/images/data-streams/streams-trade-sequence-diagram.webp new file mode 100644 index 00000000000..1300526a444 Binary files /dev/null and b/public/images/data-streams/streams-trade-sequence-diagram.webp differ diff --git a/public/samples/DataStreams/ClientReportsVerifier.sol b/public/samples/DataStreams/ClientReportsVerifier.sol new file mode 100644 index 00000000000..4a7a3544147 --- /dev/null +++ b/public/samples/DataStreams/ClientReportsVerifier.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {Common} from "@chainlink/contracts/src/v0.8/llo-feeds/libraries/Common.sol"; +import {IRewardManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol"; +import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol"; +import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; + +using SafeERC20 for IERC20; + +/** + * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE FOR DEMONSTRATION PURPOSES. + * DO NOT USE THIS CODE IN PRODUCTION. + */ + +// Custom interfaces for IVerifierProxy and IFeeManager +interface IVerifierProxy { + /** + * @notice Verifies that the data encoded has been signed. + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payload The encoded data to be verified, including the signed + * report. + * @param parameterPayload Fee metadata for billing. In the current implementation, + * this consists of the abi-encoded address of the ERC-20 token used for fees. + * @return verifierResponse The encoded report from the verifier. + */ + function verify( + bytes calldata payload, + bytes calldata parameterPayload + ) external payable returns (bytes memory verifierResponse); + + /** + * @notice Verifies multiple reports in bulk, ensuring that each is signed correctly, + * routes them to the appropriate verifier, and handles billing for the verification process. + * @param payloads An array of encoded data to be verified, where each entry includes + * the signed report. + * @param parameterPayload Fee metadata for billing. In the current implementation, + * this consists of the abi-encoded address of the ERC-20 token used for fees. + * @return verifiedReports An array of encoded reports returned from the verifier. + */ + function verifyBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload + ) external payable returns (bytes[] memory verifiedReports); + + function s_feeManager() external view returns (IVerifierFeeManager); +} + +interface IFeeManager { + /** + * @notice Calculates the fee and reward associated with verifying a report, including discounts for subscribers. + * This function assesses the fee and reward for report verification, applying a discount for recognized subscriber addresses. + * @param subscriber The address attempting to verify the report. A discount is applied if this address + * is recognized as a subscriber. + * @param unverifiedReport The report data awaiting verification. The content of this report is used to + * determine the base fee and reward, before considering subscriber discounts. + * @param quoteAddress The payment token address used for quoting fees and rewards. + * @return fee The fee assessed for verifying the report, with subscriber discounts applied where applicable. + * @return reward The reward allocated to the caller for successfully verifying the report. + * @return totalDiscount The total discount amount deducted from the fee for subscribers. + */ + 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); + + function i_nativeAddress() external view returns (address); + + function i_rewardManager() external view returns (address); +} + +/** + * @dev This contract implements functionality to verify Data Streams reports from + * the Streams Direct API or WebSocket connection, with payment in LINK tokens. + */ +contract ClientReportsVerifier { + error NothingToWithdraw(); // Thrown when a withdrawal attempt is made but the contract holds no tokens of the specified type. + error NotOwner(address caller); // Thrown when a caller tries to execute a function that is restricted to the contract's owner. + + struct Report { + 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; // Base cost to validate a transaction using the report, denominated in the chain’s native token (WETH/ETH) + uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK + uint32 expiresAt; // Latest timestamp where the report can be verified onchain + int192 price; // DON consensus median price, carried to 8 decimal places + int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation + int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation + } + + IVerifierProxy public s_verifierProxy; + + address private s_owner; + int192 public last_decoded_price; + + event DecodedPrice(int192); + + /** + * @param _verifierProxy The address of the VerifierProxy contract. + * You can find these addresses on https://docs.chain.link/data-streams/stream-ids + */ + constructor(address _verifierProxy) { + s_owner = msg.sender; + s_verifierProxy = IVerifierProxy(_verifierProxy); + } + + /// @notice Checks if the caller is the owner of the contract. + modifier onlyOwner() { + if (msg.sender != s_owner) revert NotOwner(msg.sender); + _; + } + + /** + * @notice Verifies a report and handles fee payment. + * @dev Decodes the unverified report, calculates fees, approves token spending, and verifies the report. + * Emits a DecodedPrice event upon successful verification and stores the price from the report in `last_decoded_price`. + * @param unverifiedReport The encoded report data to be verified. + */ + function verifyReport(bytes memory unverifiedReport) external { + // Report verification fees + IFeeManager feeManager = IFeeManager( + address(s_verifierProxy.s_feeManager()) + ); + + IRewardManager rewardManager = IRewardManager( + address(feeManager.i_rewardManager()) + ); + + (, /* bytes32[3] reportContextData */ bytes memory reportData) = abi + .decode(unverifiedReport, (bytes32[3], bytes)); + + address feeTokenAddress = feeManager.i_linkAddress(); + + (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 = s_verifierProxy.verify( + unverifiedReport, + abi.encode(feeTokenAddress) + ); + + // Decode verified report data into a Report struct + // If your report is a PremiumReport, you should decode it as a PremiumReport + Report memory verifiedReport = abi.decode(verifiedReportData, (Report)); + + // Log price from report + emit DecodedPrice(verifiedReport.price); + + // Store the price from the report + last_decoded_price = verifiedReport.price; + } + + /** + * @notice Withdraws all tokens of a specific ERC20 token type to a beneficiary address. + * @dev Utilizes SafeERC20's safeTransfer for secure token transfer. Reverts if the contract's balance of the specified token is zero. + * @param _beneficiary Address to which the tokens will be sent. Must not be the zero address. + * @param _token Address of the ERC20 token to be withdrawn. Must be a valid ERC20 token contract. + */ + function withdrawToken( + address _beneficiary, + address _token // LINK token address on Arbitrum Sepolia: 0x779877A7B0D9E8603169DdbD7836e478b4624789 + ) public onlyOwner { + // Retrieve the balance of this contract + uint256 amount = IERC20(_token).balanceOf(address(this)); + + // Revert if there is nothing to withdraw + if (amount == 0) revert NothingToWithdraw(); + + IERC20(_token).safeTransfer(_beneficiary, amount); + } +} diff --git a/public/samples/DataStreams/StreamsDirect/client.go b/public/samples/DataStreams/StreamsDirect/client.go new file mode 100644 index 00000000000..cd89530d03a --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/client.go @@ -0,0 +1,170 @@ +// client.go + +package client + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +type SingleReport struct { + FeedID hexutil.Bytes `json:"feedID"` + ValidFromTimestamp uint32 `json:"validFromTimestamp"` + ObservationsTimestamp uint32 `json:"observationsTimestamp"` + FullReport hexutil.Bytes `json:"fullReport"` +} + +type SingleReportResponse struct { + Report SingleReport `json:"report"` +} + +type BulkReportResponse struct { + Reports []SingleReport `json:"reports"` +} + +const ( + path = "/api/v1/reports" + bulkPath = "/api/v1/reports/bulk" +) + +func GenerateHMAC(method string, path string, body []byte, clientId string, timestamp int64, userSecret string) string { + serverBodyHash := sha256.New() + serverBodyHash.Write(body) + serverBodyHashString := fmt.Sprintf("%s %s %s %s %d", + method, + path, + hex.EncodeToString(serverBodyHash.Sum(nil)), + clientId, + timestamp) + fmt.Println("Generating HMAC with the following: ", serverBodyHashString) + signedMessage := hmac.New(sha256.New, []byte(userSecret)) + signedMessage.Write([]byte(serverBodyHashString)) + userHmac := hex.EncodeToString(signedMessage.Sum(nil)) + return userHmac +} + +func GenerateAuthHeaders(method string, pathAndParams string, clientId string, userSecret string) http.Header { + header := http.Header{} + timestamp := time.Now().UTC().UnixMilli() + hmacString := GenerateHMAC(method, pathAndParams, []byte(""), clientId, timestamp, userSecret) + + header.Add("Authorization", clientId) + header.Add("X-Authorization-Timestamp", strconv.FormatInt(timestamp, 10)) + header.Add("X-Authorization-Signature-SHA256", hmacString) + return header +} + +func FetchSingleReportSingleFeed(feedId string) (SingleReport, error) { + baseUrl := os.Getenv("BASE_URL") // Example: api.testnet-dataengine.chain.link + clientId := os.Getenv("CLIENT_ID") // Example: "00000000-0000-0000-0000-000000000000" + userSecret := os.Getenv("CLIENT_SECRET") // Example: "your-secret" + + timestamp := time.Now().UTC().UnixMilli() - 500 + + params := url.Values{ + "feedID": {feedId}, + "timestamp": {fmt.Sprintf("%d", timestamp/1000)}, + } + + req := &http.Request{ + Method: http.MethodGet, + URL: &url.URL{ + Scheme: "https", + Host: baseUrl, + Path: path, + RawQuery: params.Encode(), + }, + } + req.Header = GenerateAuthHeaders(req.Method, req.URL.RequestURI(), clientId, userSecret) + fmt.Println("base: ", baseUrl) + fmt.Println("header: ", req.Header) + fmt.Println("params: ", params) + + rawRes, err := http.DefaultClient.Do(req) + if err != nil { + return SingleReport{}, err + } + defer rawRes.Body.Close() + + body, err := io.ReadAll(rawRes.Body) + if err != nil { + return SingleReport{}, err + } + + if rawRes.StatusCode != http.StatusOK { + // Error messages are typically descriptive + return SingleReport{}, fmt.Errorf("unexpected status code %d: %v", rawRes.StatusCode, string(body)) + } + + var res SingleReportResponse + err = json.Unmarshal(body, &res) + if err != nil { + return SingleReport{}, err + } + + return res.Report, nil +} + +func FetchSingleReportManyFeeds(feedIds []string) ([]SingleReport, error) { + baseUrl := os.Getenv("BASE_URL") //Example: api.testnet-dataengine.chain.link + clientId := os.Getenv("CLIENT_ID") // Example: "00000000-0000-0000-0000-000000000000" + userSecret := os.Getenv("CLIENT_SECRET") // Example: "your-secret" + + timestamp := time.Now().UTC().UnixMilli() - 500 + + params := url.Values{ + "feedIDs": {strings.Join(feedIds, ",")}, + "timestamp": {fmt.Sprintf("%d", timestamp/1000)}, + } + + req := &http.Request{ + Method: http.MethodGet, + URL: &url.URL{ + Scheme: "https", + Host: baseUrl, + Path: bulkPath, + RawQuery: params.Encode(), + }, + } + + req.Header = GenerateAuthHeaders(req.Method, req.URL.RequestURI(), clientId, userSecret) + fmt.Println("base: ", baseUrl) + fmt.Println("header: ", req.Header) + fmt.Println("params: ", params) + + rawRes, err := http.DefaultClient.Do(req) + if err != nil { + return []SingleReport{}, err + } + defer rawRes.Body.Close() + + body, err := io.ReadAll(rawRes.Body) + if err != nil { + return []SingleReport{}, err + } + + if rawRes.StatusCode != http.StatusOK { + // Error messages are typically descriptive + return []SingleReport{}, fmt.Errorf("unexpected status code %d: %v", rawRes.StatusCode, string(body)) + } + + var res BulkReportResponse + err = json.Unmarshal(body, &res) + if err != nil { + return []SingleReport{}, err + } + + return res.Reports, nil +} \ No newline at end of file diff --git a/public/samples/DataStreams/StreamsDirect/decoder.go b/public/samples/DataStreams/StreamsDirect/decoder.go new file mode 100644 index 00000000000..dcdbe6887ad --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/decoder.go @@ -0,0 +1,129 @@ +// decoder.go + +package internal + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/pkg/errors" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + v3report "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/types" +) + +type ReportWithContext struct { + FeedId mercuryutils.FeedID + FeedVersion mercuryutils.FeedVersion + V3Report *v3report.Report + Round uint8 + Epoch uint32 + Digest []byte +} + +type FullReport struct { + ReportContext [3][32]byte + ReportBlob []byte + RawRs [][32]byte + RawSs [][32]byte + RawVs [32]byte +} + +func mustNewType(t string) abi.Type { + result, err := abi.NewType(t, "", []abi.ArgumentMarshaling{}) + if err != nil { + panic(fmt.Sprintf("Unexpected error during abi.NewType: %s", err)) + } + return result +} + +var schema = abi.Arguments{ + {Name: "reportContext", Type: mustNewType("bytes32[3]")}, + {Name: "reportBlob", Type: mustNewType("bytes")}, + {Name: "rawRs", Type: mustNewType("bytes32[]")}, + {Name: "rawSs", Type: mustNewType("bytes32[]")}, + {Name: "rawVs", Type: mustNewType("bytes32")}, +} + +/* + DecodeFullReport reads the "fullReport" from the API response into a struct containing the report context, report data, + and raw signatures. This functions requires no prep to use, because the schema for the "fullReport" blob is + common among all report versions (basic, premium, etc), +*/ + +func DecodeFullReport(fullReport []byte) (*FullReport, error) { + values, err := schema.Unpack(fullReport) + if err != nil { + return nil, fmt.Errorf("failed to decode FullReport: %w", err) + } + decoded := new(FullReport) + if err = schema.Copy(decoded, values); err != nil { + return nil, fmt.Errorf("failed to copy FullReport values to struct: %w", err) + } + + return decoded, nil +} + +/* +DecodeReportData takes the report blob (FullReport.ReportBlob), extracts the feeds id, calculates the version from the feed id, +and finally decodes the report blob using the lib that correlates with the version. The resulting interface can be cast into +the correct report type as needed. +*/ +func DecodeReportData(reportBlob []byte) (mercuryutils.FeedID, interface{}, error) { + feedIdAbi := abi.Arguments{ + {Name: "feedId", Type: mustNewType("bytes32")}, + } + reportElements := map[string]interface{}{} + if err := feedIdAbi.UnpackIntoMap(reportElements, reportBlob); err != nil { + return mercuryutils.FeedID{}, nil, err + } + feedIdInterface, ok := reportElements["feedId"] + if !ok { + return mercuryutils.FeedID{}, nil, errors.Errorf("unpacked ReportBlob has no 'feedId'") + } + feedIdBytes, ok := feedIdInterface.([32]byte) + if !ok { + return mercuryutils.FeedID{}, nil, errors.Errorf("cannot cast ReportBlob feedId to [32]byte, type is %T", feedIdBytes) + } + feedID := mercuryutils.FeedID(feedIdBytes) + + switch feedID.Version() { + case mercuryutils.REPORT_V3: + res, err := v3report.Decode(reportBlob) + return feedID, res, err + default: + return mercuryutils.FeedID{}, nil, errors.Errorf("unknown report version %d", feedID.Version()) + } +} + +/* +DecodeFullReportAndReportData takes the full report payload, decodes the fullReport blob, and then decodes the report data. +*/ +func DecodeFullReportAndReportData(payload []byte) (*ReportWithContext, error) { + fullReport, err := DecodeFullReport(payload) + if err != nil { + return nil, err + } + + feedID, report, err := DecodeReportData(fullReport.ReportBlob) + if err != nil { + return nil, err + } + + result := &ReportWithContext{ + FeedId: feedID, + FeedVersion: feedID.Version(), + Digest: fullReport.ReportContext[0][:], + Round: fullReport.ReportContext[1][31], + Epoch: binary.BigEndian.Uint32(fullReport.ReportContext[1][32-5 : 32-1]), + } + + switch feedID.Version() { + case mercuryutils.REPORT_V3: + result.V3Report = report.(*v3report.Report) + default: + return nil, errors.Errorf("unknown report version %d", feedID.Version()) + } + + return result, nil +} \ No newline at end of file diff --git a/public/samples/DataStreams/StreamsDirect/mod.go b/public/samples/DataStreams/StreamsDirect/mod.go new file mode 100644 index 00000000000..89c5247a492 --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/mod.go @@ -0,0 +1,24 @@ +module data-streams-direct + +go 1.21 + +require ( + github.com/ethereum/go-ethereum v1.12.2 // Ethereum blockchain interaction library + github.com/pkg/errors v0.9.1 // Library for handling errors + github.com/smartcontractkit/chainlink/v2 v2.2.1-0.20230823171354-1ead9ee6f6bb // Chainlink core components library +) + +replace ( + // Resolves version mismatch between cosmosSDK and hdevalence/ed25519consensus + filippo.io/edwards25519 => filippo.io/edwards25519 v1.0.0-rc.1 + + // Adds ARM support by updating CosmWasm to v1.2.4 + github.com/CosmWasm/wasmvm => github.com/CosmWasm/wasmvm v1.2.4 + + //// Fix go mod tidy issue for ambiguous imports from go-ethereum + //// See https://github.com/ugorji/go/issues/279 + github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.1 + + // Aligns protobuf version with cosmos SDK requirements + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 +) \ No newline at end of file diff --git a/public/samples/DataStreams/StreamsDirect/multipleFeeds.go b/public/samples/DataStreams/StreamsDirect/multipleFeeds.go new file mode 100644 index 00000000000..19c03dc332a --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/multipleFeeds.go @@ -0,0 +1,77 @@ +package main + +import ( + "data-streams-direct/client" + "data-streams-direct/internal" + "fmt" + "log" + "os" +) + +func main() { + // Ensure the correct number of arguments are provided + if len(os.Args) < 2 { + log.Fatalf("Usage: %s ...", os.Args[0]) + } + + // Retrieve the feed IDs from command line arguments + feedIds := os.Args[1:] + + // Fetch reports for all provided feed IDs + reports, err := client.FetchSingleReportManyFeeds(feedIds) + if err != nil { + log.Fatalf("Failed to fetch reports: %v", err) + } + + // Slice to store decoded reports + var decodedReports []*internal.ReportWithContext + + // Process each report fetched + for index, report := range reports { + // Decode the full report data + decodedReport, err := internal.DecodeFullReportAndReportData(report.FullReport) + if err != nil { + log.Fatalf("Failed to decode report: %v", err) + } + decodedReports = append(decodedReports, decodedReport) + + // Print the full report in hex format as the payload + printPayload(report.FullReport, feedIds[index]) + } + + // Print details of all decoded reports + printReportDetails(decodedReports) +} + +// Helper function to print the full report (payload) in hex format +func printPayload(payload []byte, feedId string) { + fmt.Println("") + fmt.Printf("Payload for onchain verification for Feed ID %s\n", feedId) + fmt.Println("=============================================================") + fmt.Printf("Payload (hexadecimal): 0x%x\n", payload) + fmt.Println("-------------------------------------------------------------") + fmt.Println() +} + +// Helper function to print details of the decoded reports +func printReportDetails(reports []*internal.ReportWithContext) { + fmt.Println("") + fmt.Println("Report Details") + fmt.Println("==============") + for _, report := range reports { + fmt.Printf("Feed ID: %s\n", report.FeedId) + if report.V3Report != nil { + fmt.Println("Decoded V3 Report Details:") + fmt.Println("-------------------------") + fmt.Printf("Valid From Timestamp: %d\n", report.V3Report.ValidFromTimestamp) + fmt.Printf("Observations Timestamp: %d\n", report.V3Report.ObservationsTimestamp) + fmt.Printf("Native Fee: %s\n", report.V3Report.NativeFee.String()) + fmt.Printf("Link Fee: %s\n", report.V3Report.LinkFee.String()) + fmt.Printf("Expires At: %d\n", report.V3Report.ExpiresAt) + fmt.Printf("Benchmark Price: %s\n", report.V3Report.BenchmarkPrice.String()) + fmt.Printf("Bid: %s\n", report.V3Report.Bid.String()) + fmt.Printf("Ask: %s\n", report.V3Report.Ask.String()) + fmt.Println("------------------------------------------------") + } + } +} diff --git a/public/samples/DataStreams/StreamsDirect/singleFeed.go b/public/samples/DataStreams/StreamsDirect/singleFeed.go new file mode 100644 index 00000000000..2c12dd5549e --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/singleFeed.go @@ -0,0 +1,73 @@ +// main.go for a single feed + +package main + +import ( + "data-streams-direct/client" + "data-streams-direct/internal" + "fmt" + "log" + "os" +) + +func main() { + // Check if a feed ID has been provided as an argument + if len(os.Args) < 2 { + log.Fatalf("Usage: %s ", os.Args[0]) + } + + // Retrieve the feedId from the CL arguments + feedId := os.Args[1] + + // Fetch the report for the specified feedId + report, err := client.FetchSingleReportSingleFeed(feedId) + if err != nil { + log.Fatalf("Failed to fetch report: %v", err) + } + + // Decode the full report data + decodedReport, err := internal.DecodeFullReportAndReportData(report.FullReport) + if err != nil { + log.Fatalf("Failed to decode report: %v", err) + } + + // Print details of the decoded report + printReportDetails(decodedReport) + + // Print the full report in hex format as the payload + printPayload(report.FullReport) +} + +// Helper function to print the full report (payload) in hex format +func printPayload(payload []byte) { + fmt.Println("") + fmt.Println("Payload for onchain verification") + fmt.Println("=========================================") + fmt.Printf("Payload (hexadecimal): 0x%x\n", payload) // Adding '0x' prefix for hexadecimal representation + fmt.Println("------------------------------------------------") + fmt.Println() +} + + +// Helper function to print details of the decoded report +func printReportDetails(report *internal.ReportWithContext) { + fmt.Println("") + fmt.Println("Report Details") + fmt.Println("==============") + fmt.Printf("Feed ID: %s\n", report.FeedId) + fmt.Println() + + if report.V3Report != nil { + fmt.Println("Decoded V3 Report Details:") + fmt.Println("-------------------------") + fmt.Printf("Valid From Timestamp: %d\n", report.V3Report.ValidFromTimestamp) + fmt.Printf("Observations Timestamp: %d\n", report.V3Report.ObservationsTimestamp) + fmt.Printf("Native Fee: %s\n", report.V3Report.NativeFee.String()) + fmt.Printf("Link Fee: %s\n", report.V3Report.LinkFee.String()) + fmt.Printf("Expires At: %d\n", report.V3Report.ExpiresAt) + fmt.Printf("Benchmark Price: %s\n", report.V3Report.BenchmarkPrice.String()) + fmt.Printf("Bid: %s\n", report.V3Report.Bid.String()) + fmt.Printf("Ask: %s\n", report.V3Report.Ask.String()) + fmt.Println() + } +} diff --git a/public/samples/DataStreams/StreamsDirect/ws/clientWs.go b/public/samples/DataStreams/StreamsDirect/ws/clientWs.go new file mode 100644 index 00000000000..8d17155c1ed --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/ws/clientWs.go @@ -0,0 +1,168 @@ +// clientWs.go + +package client + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "log" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/gorilla/websocket" +) + +// Constants for ping interval, pong timeout, write timeout, and retry backoff +const pingInterval = 5 * time.Second +const pongTimeout = 10 * time.Second +const writeTimeout = 5 * time.Second + +// NewReportWSMessage is the struct that we'll send to a subscribed client's MessageChan to be forwarded to the client's websocket connection +type NewReportWSMessage struct { + Report struct { + FeedId hexutil.Bytes `json:"feedID"` + FullReport hexutil.Bytes `json:"fullReport"` + } `json:"report"` +} + +const ( + wsPath = "/api/v1/ws" +) + +func GenerateHMAC(method string, path string, body []byte, clientId string, timestamp int64, userSecret string) string { + serverBodyHash := sha256.New() + serverBodyHash.Write(body) + serverBodyHashString := fmt.Sprintf("%s %s %s %s %d", + method, + path, + hex.EncodeToString(serverBodyHash.Sum(nil)), + clientId, + timestamp) + fmt.Println("Generating HMAC with the following: ", serverBodyHashString) + signedMessage := hmac.New(sha256.New, []byte(userSecret)) + signedMessage.Write([]byte(serverBodyHashString)) + userHmac := hex.EncodeToString(signedMessage.Sum(nil)) + return userHmac +} + +func GenerateAuthHeaders(method string, pathAndParams string, clientId string, userSecret string) http.Header { + header := http.Header{} + timestamp := time.Now().UTC().UnixMilli() + hmacString := GenerateHMAC(method, pathAndParams, []byte(""), clientId, timestamp, userSecret) + + header.Add("Authorization", clientId) + header.Add("X-Authorization-Timestamp", strconv.FormatInt(timestamp, 10)) + header.Add("X-Authorization-Signature-SHA256", hmacString) + return header +} + +// connectAndListen connects to the WebSocket server and starts listening for messages. +// It also handles ping/pong communication to keep the connection alive. +func ConnectAndListen(ctx context.Context, feedIds []string) error { + conn, err := openWebsocketConnection(ctx, feedIds) + if err != nil { + return err + } + defer conn.Close() + + // Start the ping/pong handling + go pingLoop(ctx, conn) + + // Set the initial read deadline + err = conn.SetReadDeadline(time.Now().Add(pongTimeout)) + if err != nil { + return err + } + + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + _, msg, err := conn.ReadMessage() + if err != nil { + return err + } + + var decoded NewReportWSMessage + err = json.Unmarshal(msg, &decoded) + if err != nil { + return fmt.Errorf("failed to unmarshal message: %w", err) + } + + // From here, you have decoded.FeedId and decoded.FullReport + // See examples for decoding the full report in other code snippets + fmt.Println("Received the following message: ", decoded) + + // There may be some latency between when you receive a message and when the report is retrievable + // So pause for a minute before taking action on the message + time.Sleep(500 * time.Millisecond) + } + } +} + +// openWebsocketConnection opens a WebSocket connection to the server. +func openWebsocketConnection(ctx context.Context, feedIds []string) (*websocket.Conn, error) { + baseUrl := os.Getenv("BASE_URL") // Example: https://ws.testnet-dataengine.chain.link + clientId := os.Getenv("CLIENT_ID") // Example: "00000000-0000-0000-0000-000000000000" + userSecret := os.Getenv("CLIENT_SECRET") // Example: "your-secret" + + if len(feedIds) == 0 { + return nil, fmt.Errorf("no feed ID(s) provided") + } + + params := url.Values{ + "feedIDs": {strings.Join(feedIds, ",")}, + } + + reqURL := &url.URL{ + Scheme: "wss", // Note the scheme here + Host: baseUrl, + Path: wsPath, + RawQuery: params.Encode(), + } + + headers := GenerateAuthHeaders("GET", reqURL.RequestURI(), clientId, userSecret) + conn, _, err := websocket.DefaultDialer.DialContext(ctx, reqURL.String(), headers) + if err != nil { + return nil, err + } + + // Add the Pong handler + conn.SetPongHandler(func(string) error { + log.Println("Websocket:", "Received pong...") + err := conn.SetReadDeadline(time.Now().Add(pongTimeout)) + return err + }) + + return conn, nil +} + +// PingPongLoop is a function that handles sending ping messages to the websocket and handles pong messages received +func pingLoop(ctx context.Context, conn *websocket.Conn) { + ticker := time.NewTicker(pingInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + log.Println("Websocket:", "Sending ping...") + err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeTimeout)) + if err != nil { + log.Printf("Failed to send ping: %v", err) + return + } + } + } +} \ No newline at end of file diff --git a/public/samples/DataStreams/StreamsDirect/ws/decodeFullReportHex.go b/public/samples/DataStreams/StreamsDirect/ws/decodeFullReportHex.go new file mode 100644 index 00000000000..3bfb7e61637 --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/ws/decodeFullReportHex.go @@ -0,0 +1,40 @@ +// decodeFullReportHex.go + +package main + +import ( + "data-streams-direct-ws/internal" + "encoding/hex" + "fmt" + "log" +) + +func main() { + // Sample FullReport payload extracted from the WebSocket message as a hex string + fullReportHex := "00067f14c763070bec1de1118aceeed1546878ab24e3213de21127249adabcbd0000000000000000000000000000000000000000000000000000000011f0c90b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000240010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000027bbaff688c906a3e20a34fe951715d1018d262a5b66e38eda027a674cd1b0000000000000000000000000000000000000000000000000000000065cdcd950000000000000000000000000000000000000000000000000000000065cdcd95000000000000000000000000000000000000000000000000000020b5e9c686e80000000000000000000000000000000000000000000000000011b8f926846fb80000000000000000000000000000000000000000000000000000000065cf1f15000000000000000000000000000000000000000000000096ba314c8f5ec2800000000000000000000000000000000000000000000000000000000000000000029c85f7bb779ce5316821a6efd3bdc843e32f1438bad24a6b269871ade0c166d2142574b78d7a75d3cb855e51caf9cb36e1d598b43c881989251bd2c450624a2600000000000000000000000000000000000000000000000000000000000000021c1e5c393a57a24f2c698e23c59e6a954a9ef006b63c7f58eeabdd0bbeff21e5353ddef53e9beb93f628cd23e2cc28750533f444559622640bcf7dcc935d66af" + + // Convert the hex string to a byte slice + fullReportPayload, err := hex.DecodeString(fullReportHex) + if err != nil { + log.Fatalf("Failed to decode hex string: %v", err) + } + + // Decode the full report + decodedReport, err := internal.DecodeFullReportAndReportData(fullReportPayload) + if err != nil { + log.Fatalf("Failed to decode report: %v", err) + } + + fmt.Printf("Decoded Report: %+v\n", decodedReport) + + if decodedReport.FeedVersion == 3 && decodedReport.V3Report != nil { + fmt.Printf("Valid From Timestamp: %d\n", decodedReport.V3Report.ValidFromTimestamp) + fmt.Printf("Observations Timestamp: %d\n", decodedReport.V3Report.ObservationsTimestamp) + fmt.Printf("Native Fee: %s\n", decodedReport.V3Report.NativeFee.String()) + fmt.Printf("Link Fee: %s\n", decodedReport.V3Report.LinkFee.String()) + fmt.Printf("Expires At: %d\n", decodedReport.V3Report.ExpiresAt) + fmt.Printf("Benchmark Price: %s\n", decodedReport.V3Report.BenchmarkPrice.String()) + fmt.Printf("Bid: %s\n", decodedReport.V3Report.Bid.String()) + fmt.Printf("Ask: %s\n", decodedReport.V3Report.Ask.String()) + } +} \ No newline at end of file diff --git a/public/samples/DataStreams/StreamsDirect/ws/mod.go b/public/samples/DataStreams/StreamsDirect/ws/mod.go new file mode 100644 index 00000000000..8a4e20baa3c --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/ws/mod.go @@ -0,0 +1,25 @@ +module data-streams-direct-ws + +go 1.21 + +require ( + github.com/ethereum/go-ethereum v1.12.2 // Ethereum blockchain interaction library + github.com/gorilla/websocket v1.5.0 // Websocket library + github.com/pkg/errors v0.9.1 // Library for handling errors + github.com/smartcontractkit/chainlink/v2 v2.2.1-0.20230823171354-1ead9ee6f6bb // Chainlink core components library +) + +replace ( + // Resolves version mismatch between cosmosSDK and hdevalence/ed25519consensus + filippo.io/edwards25519 => filippo.io/edwards25519 v1.0.0-rc.1 + + // Adds ARM support by updating CosmWasm to v1.2.4 + github.com/CosmWasm/wasmvm => github.com/CosmWasm/wasmvm v1.2.4 + + //// Fix go mod tidy issue for ambiguous imports from go-ethereum + //// See https://github.com/ugorji/go/issues/279 + github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.1 + + // Aligns protobuf version with cosmos SDK requirements + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 +) \ No newline at end of file diff --git a/public/samples/DataStreams/StreamsDirect/ws/wsConnectAndListen.go b/public/samples/DataStreams/StreamsDirect/ws/wsConnectAndListen.go new file mode 100644 index 00000000000..7df83a4fe08 --- /dev/null +++ b/public/samples/DataStreams/StreamsDirect/ws/wsConnectAndListen.go @@ -0,0 +1,25 @@ +// main.go + +package main + +import ( + "context" + "data-streams-direct-ws/client" + "log" + "os" +) + +func main() { + if len(os.Args) < 2 { + log.Fatalf("Usage: %s ...", os.Args[0]) + } + + feedIds := os.Args[1:] + + ctx := context.Background() + + // Pass feed IDs to the ConnectAndListen function + if err := client.ConnectAndListen(ctx, feedIds); err != nil { + log.Fatalf("Error connecting and listening: %v", err) + } +} diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 0c05349524b..4297f90fd20 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -216,15 +216,45 @@ export const SIDEBAR: Partial> = { url: "data-streams/getting-started-hardhat", }, { - title: "Stream IDs", + title: "Data Streams Feed IDs", url: "data-streams/stream-ids", }, + { + title: "Billing", + url: "data-streams/billing", + }, { title: "Release Notes", url: "data-streams/release-notes", }, ], }, + { + section: "Guides", + contents: [ + { + title: "Fetch and decode reports (REST API)", + url: "data-streams/tutorials/streams-direct-api", + }, + { + title: "Stream and decode reports (WebSocket)", + url: "data-streams/tutorials/streams-direct-ws", + }, + { + title: "Verify report data onchain", + url: "data-streams/tutorials/streams-direct-onchain-verification", + }, + ], + }, + { + section: "Concepts", + contents: [ + { + title: "Architecture", + url: "data-streams/architecture", + }, + ], + }, { section: "Reference", contents: [ @@ -233,8 +263,26 @@ export const SIDEBAR: Partial> = { url: "data-streams/reference/report-schema", }, { - title: "Interfaces", - url: "data-streams/reference/interfaces", + title: "Streams Trade Interface", + url: "data-streams/reference/streams-trade-interface", + }, + { + title: "Streams Direct Interface", + url: "data-streams/reference/streams-direct-interface-api", + children: [ + { + title: "REST API", + url: "data-streams/reference/streams-direct-interface-api", + }, + { + title: "WebSocket", + url: "data-streams/reference/streams-direct-interface-ws", + }, + { + title: "Onchain report data verification", + url: "data-streams/reference/streams-direct-onchain-verification", + }, + ], }, ], }, diff --git a/src/content/data-streams/architecture.mdx b/src/content/data-streams/architecture.mdx new file mode 100644 index 00000000000..0db3a4b9abe --- /dev/null +++ b/src/content/data-streams/architecture.mdx @@ -0,0 +1,77 @@ +--- +section: dataStreams +title: "Data Streams Architecture" +whatsnext: + { + "Find the list of available stream IDs.": "/data-streams/stream-ids", + "Find the schema of data to expect from Data Streams reports.": "/data-streams/reference/report-schema", + } +--- + +import { Aside, ClickToZoom } from "@components" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + +## High Level Architecture + +Chainlink Data Streams has the following core components: + +- **A Chainlink Decentralized Oracle Network (DON):** This DON operates similarly to the DONs that power [Chainlink Data Feeds](/data-feeds), but the key difference is that it signs and delivers reports to the Chainlink Data Streams Aggregation Network rather than delivering answers onchain directly. This allows the Data Streams DON to deliver reports more frequently for time-sensitive applications. Nodes in the DON retrieve data from many different data providers, reach a concensus about the median price of an asset, sign a report including that data, and deliver the report to the Data Streams Aggregation Network. +- **The Chainlink Data Streams Aggregation Network:** The Data Streams Aggregation Network stores the signed reports and makes them available for retrieval. It can deliver these reports to Chainlink Automation upon request (Streams Trade) or provide direct access via the API (Streams Direct). +- **The Chainlink Verifier Contract:** This contract verifies the signature from the DON to cryptographically guarantee that the report has not been altered from the time that the DON reached concensus to the point where you use the data in your application. + +## Streams Trade Architecture + +Using Chainlink Automation with Data Streams automates trade execution and mitigates frontrunning by executing the transaction before the data is recorded onchain. Chainlink Automation requests data from the Data Streams Aggregation Network. It executes transactions only in response to the data and the verified report, so the transaction is executed correctly and independently from the decentralized application itself. + + + +#### Example trading flow using Streams Trade + +One example of how to use Data Streams with Automation is in a decentralized exchange. An example flow might work using the following process: + + + +1. A user initiates a trade by confirming an `initiateTrade` transaction in their wallet. +1. The onchain contract for the decentralized exchange responds by emitting a [log trigger](/chainlink-automation/concepts/automation-concepts#upkeeps-and-triggers) event. +1. The Automation upkeep monitors the contract for the event. When Automation detects the event, it runs the `checkLog` function specified in the upkeep contract. The upkeep is defined by the decentralized exchange. +1. The `checkLog` function uses a `revert` with a custom error called `StreamsLookup`. This approach aligns with [EIP-3668](https://eips.ethereum.org/EIPS/eip-3668#use-of-revert-to-convey-call-information) and conveys the required information through the data in the `revert` custom error. +1. Automation monitors the `StreamsLookup` custom error that triggers Data Streams to process the offchain data request. Data Streams then returns the requested signed report in the `checkCallback` function for Automation. +1. Automation passes the report to the Automation Registry, which executes the `performUpkeep` function defined by the decentralized exchange. The report is included as a variable in the `performUpkeep` function. +1. The `performUpkeep` function calls the `verify` function on the Data Streams onchain verifier contract and passes the report as a variable. +1. The verifier contract returns a `verifierResponse` bytes value to the upkeep. +1. If the response indicates that the report is valid, the upkeep executes the user's requested trade. If the response is invalid, the upkeep rejects the trade and notifies the user. + +This is one example of how you can combine Data Streams and Automation, but the systems are highly configurable. You can write your own log triggers or [custom logic triggers](/chainlink-automation/guides/register-upkeep) to initiate Automation upkeeps for a various array of events. You can configure the `StreamsLookup` to retrieve multiple reports. You can configure the `performUpkeep` function to perform a wide variety of actions using the report. + +Read the [Getting Started](/data-streams/getting-started) guide to learn how to build your own smart contract that retrieves reports from Data Streams using the Streams Trade implementation. + +## Streams Direct Architecture + + + +#### Example of offchain price updates with Streams Direct + +Streams Direct enables seamless offchain price updates through a mechanism designed for real-time data delivery. Here is an example of how your Client will benefit from low-latency market data directly from the Data Streams Aggregation Network. + +1. The Client opens a WebSocket connection to the Data Streams Aggregation Network to subscribe to new reports with low latency. + +1. The Data Streams Aggregation Network sends price reports via WebSocket, which gives the Client instant access to updated market data. + +1. The Client stores the price reports in a cache for quick access and use, which preserves data integrity over time. + +1. The Client regularly queries `/client/bulk` for any missed reports to ensure data completeness. + +1. If the Data Streams Aggregation Network identifies missed reports, it sends back an array of these reports. This mechanism allows the Client to update its cache and to keep the data set complete and up-to-date. + + diff --git a/src/content/data-streams/billing.mdx b/src/content/data-streams/billing.mdx new file mode 100644 index 00000000000..720bf64be56 --- /dev/null +++ b/src/content/data-streams/billing.mdx @@ -0,0 +1,24 @@ +--- +section: dataStreams +title: "Data Streams Billing" +isIndex: false +whatsnext: + { + "Learn the basics about how to retrieve Data Streams reports in the Getting Started guide.": "/data-streams/getting-started", + "Find the list of available stream IDs.": "/data-streams/stream-ids", + "Find the schema of data to expect from Data Streams reports.": "/data-streams/reference/report-schema", + } +--- + +import { Aside, ClickToZoom } from "@components" + + + +You pay to verify reports from Data Streams onchain using the verifier contract. The verification price depends on the Data Streams feed ID from which you verify a report. You pay per report verified. If you verify multiple reports in a batch, you pay for all of the reports included in that batch. + +Chainlink Data Streams supports fee payments in LINK and in alternative assets, which currently includes native blockchain gas tokens and their ERC20-wrapped version. Payments made in alternative assets have a 10% surcharge when compared to LINK payments. + +[Contact us](https://chainlinkcommunity.typeform.com/datastreams?#ref_id=docs) to learn more about Mainnet pricing. diff --git a/src/content/data-streams/getting-started.mdx b/src/content/data-streams/getting-started.mdx index cec1c1504b5..545e5c9bb2e 100644 --- a/src/content/data-streams/getting-started.mdx +++ b/src/content/data-streams/getting-started.mdx @@ -6,8 +6,8 @@ metadata: linkToWallet: true excerpt: "Learn the basics for how to get data from Chainlink Data Streams." whatsnext: { -"Find the list of available stream IDs.": "/data-streams/stream-ids", -"Find the schema of data to expect from Data Streams reports.": "/data-streams/reference/report-schema", +"Find the list of available Data Streams Feed IDs": "/data-streams/stream-ids", +"Find the schema of data to expect from Data Streams reports": "/data-streams/reference/report-schema", "Learn more about Log Trigger upkeeps": "/chainlink-automation/guides/log-trigger/", } --- diff --git a/src/content/data-streams/index.mdx b/src/content/data-streams/index.mdx index 8ae89de7284..3ddae1c5b26 100644 --- a/src/content/data-streams/index.mdx +++ b/src/content/data-streams/index.mdx @@ -4,24 +4,33 @@ title: "Chainlink Data Streams" isIndex: true whatsnext: { - "Learn the basics about how to retrieve Data Streams reports in the Getting Started guide.": "/data-streams/getting-started", - "Find the list of available stream IDs.": "/data-streams/stream-ids", - "Find the schema of data to expect from Data Streams reports.": "/data-streams/reference/report-schema", + "Learn the basics about how to retrieve Data Streams reports using the Streams Trade implementation": "/data-streams/getting-started", + "Learn how to fetch and decode Data Streams reports using the Streams Direct API": "/data-streams/tutorials/streams-direct-api", + "Find the list of available Data Streams Feed IDs": "/data-streams/stream-ids", + "Find the schema of data to expect from Data Streams reports": "/data-streams/reference/report-schema", } --- import { Aside, ClickToZoom } from "@components" -import button from "@chainlink/design-system/button.module.css" import DataStreams from "@features/data-streams/common/DataStreams.astro" -Chainlink Data Streams provides low-latency delivery of market data offchain that you can verify onchain. With Chainlink Data Streams, decentralized applications (dApps) now have on-demand access to high-frequency market data backed by decentralized and transparent infrastructure. When combined with [Chainlink Automation](/chainlink-automation/introduction), Chainlink Data Streams allows decentralized applications to automate trade execution, mitigate frontrunning, and limit bias or adverse incentives in executing non-user-triggered orders. - - +Chainlink Data Streams provides low-latency delivery of market data offchain that you can verify onchain. With Chainlink Data Streams, decentralized applications (dApps) now have on-demand access to high-frequency market data backed by decentralized and transparent infrastructure. Traditional push-based oracles provide regular updates onchain when certain price thresholds or update time periods have been met. Chainlink Data Streams is built using a new pull-based oracle design that maintains trust-minimization using onchain verification. +## Comparison to push-based oracles + +Chainlink's push-based oracles provide regular updates onchain. Chainlink Data Streams operates as a pull-based oracle where you can retrieve the data in a report and use it onchain any time. Verifying the report onchain confirms that the data was agreed upon and signed by the DON. While many applications benefit from push-based oracles and require data only after it has been verified onchain, some applications require access to data that is updated at a higher frequency and delivered with lower latency. Pull-based oracles deliver these benefits while still cryptographically signing the data to ensure its veracity. + + + +Additionally, pull-based oracles deliver data onchain more efficiently by retrieving and verifying the data only when the application needs it. For example, a decentralized exchange might retrieve a Data Streams report and verify the data onchain when a user executes a trade. A push-based oracle repeatedly delivers data onchain even when that data is not immediately required by users. + ## Use cases Pull-based oracles allow decentralized applications to access data that is updated at a high frequency and delivered with low latency, which enables several new use cases: @@ -30,48 +39,30 @@ Pull-based oracles allow decentralized applications to access data that is updat - **Options:** Pull-based oracles allow timely and precise settlement of options contracts. Additionally, Data Streams provides more detailed market liquidity data that can support dynamic onchain risk management logic. - **Prediction Markets:** Higher frequency data updates allow for applications where users can act quickly in response to real-time events and be confident in the accuracy of the data used in the settlement. -## Billing - -Chainlink Data Streams supports fee payments in LINK and in alternative assets, which currently includes native blockchain gas tokens and their ERC20-wrapped version. Payments made in alternative assets have a surcharge when compared to LINK payments. You pay to verify reports from Data Streams onchain using the verifier contract. The price of verification depends on the stream ID that you are verifying. You pay per report verified. If you verify multiple reports in a batch, you pay for all of the reports included in that batch. +## Data Streams implementations -[Contact us](https://chainlinkcommunity.typeform.com/datastreams?#ref_id=docs) to learn more about Mainnet pricing. +### Streams Trade: Using Data Streams with Chainlink Automation -## Architecture +When combined with [Chainlink Automation](/chainlink-automation/introduction), Chainlink Data Streams allows decentralized applications to automate trade execution, mitigate frontrunning, and limit bias or adverse incentives in executing non-user-triggered orders. -Chainlink Data Streams has the following core components: + -- **A Chainlink Decentralized Oracle Network (DON):** This DON operates similarly to the DONs that power [Chainlink Data Feeds](/data-feeds), but the key difference is that it signs and delivers reports to the Data Streams Aggregation Network rather than delivering answers onchain directly. This allows the Data Streams DON to deliver reports more frequently for time-sensitive applications. Nodes in the DON retrieve data from many different data providers, reach a concensus about the median price of an asset, sign a report including that data, and deliver the report to the Data Streams Aggregation Network. -- **The Data Streams Aggregation Network:** The Data Streams Aggregation Network stores the signed reports and delivers them to Chainlink Automation when it is requested. -- **The Chainlink Verifier Contract:** This contract verifies the signature from the DON to cryptographically guarantee that the report has not been altered from the time that the DON reached concensus to the point where you use the data in your application. - - - -Using Chainlink Automation with Data Streams automates trade execution and mitigates frontrunning by executing the transaction before the data is recorded onchain. Chainlink Automation executes transactions only in response to the data and the verified report, so the transaction is executed correctly and independently from the decentralized application itself. - -### Comparison to push-based oracles - -Chainlink's push-based oracles provide regular updates onchain. Chainlink Data Streams operates as a pull-based oracle where you can retrieve the data in a report and use it onchain any time. Verifying the report onchain confirms that the data was agreed upon and signed by the DON. While many applications benefit from push-based oracles and require data only after it has been verified onchain, some applications require access to data that is updated at a higher frequency and delivered with lower latency. Pull-based oracles deliver these benefits while still cryptographically signing the data to ensure its veracity. - - - -Additionally, pull-based oracles deliver data onchain more efficiently by retrieving and verifying the data only when the application needs it. For example, a decentralized exchange might retrieve a Data Streams report and verify the data onchain when a user executes a trade. A push-based oracle repeatedly delivers data onchain even when that data is not immediately required by users. +Read more about the [Streams Trade Architecture](/data-streams/architecture#streams-trade-architecture) and an [example trading flow](/data-streams/architecture#example-trading-flow-using-streams-trade), or learn how to [get started](/data-streams/getting-started) with Streams Trade. -### Example trading flow +### Streams Direct: Using Data Streams with your own bot -One example of how to use Data Streams is in a decentralized exchange. An example flow might work using the following process: +Streams Direct offers a direct approach to integrating low-latency and high-frequency data into your applications. You can use an [offchain API](/data-streams/reference/streams-direct-interface-api) to fetch reports or a [WebSocket connection](/data-streams/reference/streams-direct-interface-ws) to subscribe to report updates from the Data Streams Aggregation Network, and an onchain smart contract to [verify reports](/data-streams/reference/streams-direct-onchain-verification). - +For instance, you can use Chainlink Data Streams with the Streams Direct implementation to display indicative pricing offchain on your front end or with your bot to settle trades onchain. -1. A user initiates a trade by confirming an `initiateTrade` transaction in their wallet. -1. The onchain contract for the decentralized exchange responds by emitting a Log Trigger event. -1. The Chainlink Automation upkeep monitors the contract for the event. When Automation detects the event, it runs the `checkLog` function specified in the upkeep contract. The upkeep is defined by the decentralized exchange. -1. The `checkLog` function uses a `revert` with a custom error called `StreamsLookup`. This approach aligns with [EIP-3668](https://eips.ethereum.org/EIPS/eip-3668#use-of-revert-to-convey-call-information) and conveys the required information through the data in the `revert` custom error. -1. Chainlink Automation monitors the `StreamsLookup` custom error that triggers Chainlink Data Streams to process the offchain data request. Chainlink Data Streams then returns the requested signed report in the `checkCallback` function for Chainlink Automation. -1. Chainlink Automation passes the report to the Automation Registry, which executes the `performUpkeep` function defined by the decentralized exchange. The report is included as a variable in the `performUpkeep` function. -1. The `performUpkeep` function calls the `verify` function on the Data Streams onchain verifier contract and passes the report as a variable. -1. The verifier contract returns a `verifierResponse` bytes value to the upkeep. -1. If the response indicates that the report is valid, the upkeep executes the user's requested trade. If the response is invalid, the upkeep rejects the trade and notifies the user. +#### On-demand offchain workflows -This is one example of how you can combine Data Streams and Chainlink Automation, but the systems are highly configurable. You can write your own log triggers to initiate upkeeps on Chainlink Automation for a various array of events. You can configure the `StreamsLookup` to retrieve multiple reports. You can configure the `performUpkeep` function to perform a wide variety of actions using the report. + -Read the [Getting Started](/data-streams/getting-started) guide to learn how to build your own smart contract that retrieves reports from Chainlink Data Streams. +Explore an example of offchain price updates through Streams Direct in the [Architecture](/data-streams/architecture#streams-direct-architecture) guide, or follow this [guide](/data-streams/tutorials/streams-direct-api) to learn how to fetch and decode Data Streams reports using the Streams Direct API. diff --git a/src/content/data-streams/reference/report-schema.mdx b/src/content/data-streams/reference/report-schema.mdx index ee1795af798..717357477d3 100644 --- a/src/content/data-streams/reference/report-schema.mdx +++ b/src/content/data-streams/reference/report-schema.mdx @@ -1,10 +1,9 @@ --- section: dataStreams date: Last Modified -title: "Report Schema Reference" +title: "Report Schema" --- -import { Aside } from "@components" import DataStreams from "@features/data-streams/common/DataStreams.astro" diff --git a/src/content/data-streams/reference/streams-direct-interface-api.mdx b/src/content/data-streams/reference/streams-direct-interface-api.mdx new file mode 100644 index 00000000000..db82f62e505 --- /dev/null +++ b/src/content/data-streams/reference/streams-direct-interface-api.mdx @@ -0,0 +1,190 @@ +--- +section: dataStreams +date: Last Modified +title: "Streams Direct REST API" +--- + +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + +## Domains + +| Description | Testnet URL | Mainnet URL | +| --------------------------------------- | ------------------------------------------ | --------------------------------- | +| REST endpoint to query specific reports | https://api.testnet-dataengine.chain.link/ | https://api.dataengine.chain.link | + +## Authentication + +### Headers + +All routes require the following three headers for user authentication: + +| Header | Description | +| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Authorization | The user’s unique identifier, provided as a UUID (Universally Unique IDentifier). | +| X-Authorization-Timestamp | The current timestamp, with precision up to milliseconds. The timestamp must closely synchronize with the server time, allowing a maximum discrepancy of 5 seconds (by default). | +| X-Authorization-Signature-SHA256 | The HMAC (Hash-based Message Authentication Code) signature, generated by hashing parts of the request and its metadata using SHA-256 with a shared secret key. | + +## API endpoints + +### Return a single report at a given timestamp + +##### Endpoint + +**`/api/v1/reports`** + +| Type | Description | Parameter(s) | +| -------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| HTTP GET | Returns a single report for a given timestamp. |
  • `feedID`: A Data Streams feed ID.
  • `timestamp`: The Unix timestamp for the report.
| + +##### Sample request + +```http +GET /api/v1/reports?feedID=×tamp= +``` + +##### Sample response + +```json +{ + "report": { + "feedID": "Hex encoded feedId.", + "validFromTimestamp": "Report's earliest applicable timestamp (in seconds).", + "observationsTimestamp": "Report's latest applicable timestamp (in seconds).", + "fullReport": "A blob containing the report context and body. Encode the fee token into the payload before passing it to the contract for verification." + } +} +``` + +### Return a single report with the latest timestamp + +##### Endpoint + +**`/api/v1/reports/latest`** + +| Type | Parameter(s) | +| -------- | --------------------------------- | +| HTTP GET | `feedID`: A Data Streams feed ID. | + +##### Sample request + +```http +GET /api/v1/reports/latest?feedID= +``` + +##### Sample response + +```json +{ + "report": { + "feedID": "Hex encoded feedId.", + "validFromTimestamp": "Report's earliest applicable timestamp (in seconds).", + "observationsTimestamp": "Report's latest applicable timestamp (in seconds).", + "fullReport": "A blob containing the report context and body. Encode the fee token into the payload before passing it to the contract for verification." + } +} +``` + +### Return a report for multiple FeedIDs at a given timestamp + +##### Endpoint + +**`/api/v1/reports/bulk`** + +| Type | Description | Parameter(s) | +| -------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| HTTP GET | Return a report for multiple FeedIDs at a given timestamp. |
  • `feedIDs`: A comma-separated list of Data Streams feed IDs.
  • `timestamp`: The Unix timestamp for the reports.
| + +##### Sample request + +```http +GET /api/v1/reports/bulk?feedIDs=,,...×tamp= +``` + +##### Sample response + +```json +{ + "reports": [ + { + "feedID": "Hex encoded feedId.", + "validFromTimestamp": "Report's earliest applicable timestamp (in seconds).", + "observationsTimestamp": "Report's latest applicable timestamp (in seconds).", + "fullReport": "A blob containing the report context and body. Encode the fee token into the payload before passing it to the contract for verification." + } + //... + ] +} +``` + +### Return multiple sequential reports for a single FeedID, starting at a given timestamp + +##### Endpoint + +**`/api/v1/reports/page`** + +| Type | Description | Parameter(s) | +| -------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| HTTP GET | Return multiple sequential reports for a single FeedID, starting at a given timestamp. |
  • `feedID`: A Data Streams feed ID.
  • `startTimestamp`: The Unix timestamp for the first report.
  • `limit` (optional): The number of reports to return.
| + +##### Sample request + +```http +GET /api/v1/reports/page?feedID=&startTimestamp=&limit= +``` + +##### Sample response + +```json +{ + "reports": [ + { + "feedID": "Hex encoded feedId.", + "validFromTimestamp": "Report's earliest applicable timestamp (in seconds).", + "observationsTimestamp": "Report's latest applicable timestamp (in seconds).", + "fullReport": "A blob containing the report context and body. Encode the fee token into the payload before passing it to the contract for verification." + } + //... + ] +} +``` + +### Return all feeds you have permission to access + +##### Endpoint + +**`/api/v1/feeds`** + +| Type | Description | Parameter(s) | +| -------- | --------------------------------------------------------- | ------------ | +| HTTP GET | Return a list of all feeds you have permission to access. | None | + +##### Sample request + +```http +GET /api/v1/feeds +``` + +##### Sample response + +```json +{ + "feeds": [ + { "feedID": "hex encoded feedID1" }, + { "feedID": "hex encoded feedID2" }, + { "feedID": "hex encoded feedID3" }, + // ... + { "feedID": "hex encoded feedIDn" } + ] +} +``` + +## Error response codes + +| Status Code | Description | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 400 Bad Request | This error is triggered when:
  • There is any missing/malformed query argument.
  • Required headers are missing or provided with incorrect values.
| +| 401 Unauthorized User | This error is triggered when:
  • Authentication fails, typically because the HMAC signature provided by the client doesn't match the one expected by the server.
  • A user requests access to a feed without the appropriate permission or that does not exist.
| +| 500 Internal Server | Indicates an unexpected condition encountered by the server, preventing it from fulfilling the request. This error typically points to issues on the server side. | +| 206 Missing data (`/bulk` endpoint only) | Indicates that at least one feed ID data is missing from the report. E.g., you requested a report for feed IDs ``, ``, and `` at a given timestamp. If data for `` is missing from the report (not available yet at the specified timestamp), you get `[, ]` and a 206 response. | diff --git a/src/content/data-streams/reference/streams-direct-interface-ws.mdx b/src/content/data-streams/reference/streams-direct-interface-ws.mdx new file mode 100644 index 00000000000..10bd274134e --- /dev/null +++ b/src/content/data-streams/reference/streams-direct-interface-ws.mdx @@ -0,0 +1,64 @@ +--- +section: dataStreams +date: Last Modified +title: "Streams Direct WebSocket" +--- + +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + +## Domains + +| Description | Testnet URL | Mainnet URL | +| ------------------------------------------------ | ----------------------------------------- | ------------------------------ | +| WebSocket endpoint to subscribe to price updates | https://ws.testnet-dataengine.chain.link/ | wss://ws.dataengine.chain.link | + +## Authentication + +### Headers + +All routes require the following three headers for user authentication: + +| Header | Description | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Authorization | The user’s unique identifier, provided as a UUID (Universally Unique IDentifier). | +| X-Authorization-Timestamp | The current timestamp, with precision up to nanoseconds. The timestamp must closely synchronize with the server time, allowing a maximum discrepancy of 5 seconds (by default). | +| X-Authorization-Signature-SHA256 | The HMAC (Hash-based Message Authentication Code) signature, generated by hashing parts of the request and its metadata using SHA-256 with a shared secret key. | + +## WebSocket Connection + +Establish a streaming WebSocket connection that sends reports for the given feedID(s) after they are verified. + +##### Endpoint + +**`/api/v1/ws`** + +| Type | Parameter(s) | +| --------- | ----------------------------------------------------------- | +| WebSocket | `feedIDs`: A comma-separated list of Data Streams feed IDs. | + +##### Sample request + +```http +GET /api/v1/ws?feedIDs=,,... +``` + +##### Sample response + +```json +{ + "report": { + "feedID": "hex encoded feedId", + "fullReport": "a blob containing the report context + body, can be passed unmodified to the contract for verification" + } +} +``` + +## Error response codes + +| Status Code | Description | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 400 Bad Request | This error is triggered when:
  • There is any missing/malformed query argument.
  • Required headers are missing or provided with incorrect values.
| +| 401 Unauthorized User | This error is triggered when:
  • Authentication fails, typically because the HMAC signature provided by the client doesn't match the one expected by the server.
  • A user requests access to a feed without the appropriate permission or that does not exist.
| +| 500 Internal Server | Indicates an unexpected condition encountered by the server, preventing it from fulfilling the request. This error typically points to issues on the server side. | diff --git a/src/content/data-streams/reference/streams-direct-onchain-verification.mdx b/src/content/data-streams/reference/streams-direct-onchain-verification.mdx new file mode 100644 index 00000000000..97f60c21dc2 --- /dev/null +++ b/src/content/data-streams/reference/streams-direct-onchain-verification.mdx @@ -0,0 +1,37 @@ +--- +section: dataStreams +date: Last Modified +title: "Onchain Data Verification" +--- + +import { CodeSample, Aside } from "@components" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + + + +## Verify reports onchain + +To verify data onchain, Streams Direct requires several interfaces. + +The primary onchain interaction occurs between the `IVerifierProxy` interface and your protocol's client contract. Find the Verifier proxy address for each feed on the [Data Streams Feed IDs](/data-streams/stream-ids) page. + +### Interfaces + +- IVerifierProxy +- IFeeManager + +In the current code example for verifying reports onchain using Streams Direct, these interfaces are specified in the example itself. Imports for these interfaces will be available in the future. + +### Contract example to verify report data onchain + +This contract example allows you to verify reports and pay the verification fee in LINK tokens. Your contract must have sufficient LINK tokens to pay for the verification fee. Learn how to [fund your contract with LINK tokens](/resources/fund-your-contract). + + + + diff --git a/src/content/data-streams/reference/streams-trade-interface.mdx b/src/content/data-streams/reference/streams-trade-interface.mdx new file mode 100644 index 00000000000..02472a4a7ca --- /dev/null +++ b/src/content/data-streams/reference/streams-trade-interface.mdx @@ -0,0 +1,26 @@ +--- +section: dataStreams +date: Last Modified +title: "Streams Trade Interface" +--- + +import { Aside, CodeSample } from "@components" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + +To retrieve and verify reports, Streams Trade requires several interfaces. + +## Automation interfaces + +- [StreamsLookupCompatibleInterface](/chainlink-automation/reference/automation-interfaces#streamslookupcompatibleinterface) +- [ILogAutomation](/chainlink-automation/reference/automation-interfaces#ilogautomation) + +## Data Streams interfaces + +- IVerifierProxy +- IFeeManager + +In the code example for using Data Streams with Automation (Streams Trade), the interfaces are specified in the example itself. + + diff --git a/src/content/data-streams/release-notes.mdx b/src/content/data-streams/release-notes.mdx index bfe1b838c7e..eeb8716dacb 100644 --- a/src/content/data-streams/release-notes.mdx +++ b/src/content/data-streams/release-notes.mdx @@ -23,4 +23,4 @@ Chainlink Data Streams is available in Early Access on Arbitrum Mainnet and Arbitrum Goerli. diff --git a/src/content/data-streams/stream-ids.mdx b/src/content/data-streams/stream-ids.mdx index 421919abef3..256685fcdcf 100644 --- a/src/content/data-streams/stream-ids.mdx +++ b/src/content/data-streams/stream-ids.mdx @@ -1,10 +1,10 @@ --- section: dataStreams -title: "Stream IDs" +title: "Data Streams Feed IDs" datafeedtype: streams metadata: - title: "Stream IDs" - description: "A list of available data streams and their IDs." + title: "Data Streams Feed IDs" + description: "A list of available Data Streams feeds and their ID." date: Last Modified --- diff --git a/src/content/data-streams/tutorials/streams-direct-api.mdx b/src/content/data-streams/tutorials/streams-direct-api.mdx new file mode 100644 index 00000000000..6f06c435a65 --- /dev/null +++ b/src/content/data-streams/tutorials/streams-direct-api.mdx @@ -0,0 +1,277 @@ +--- +section: dataStreams +date: Last Modified +title: "Fetch and decode reports via a REST API" +whatsnext: + { + "Learn how to stream and decode reports via a WebSocket connection": "/data-streams/tutorials/streams-direct-ws", + "Learn how to verify your data onchain": "/data-streams/reference/streams-direct-onchain-verification", + "Find the list of available Data Streams Feed IDs": "/data-streams/stream-ids", + } +--- + +import { CodeSample, CopyText } from "@components" +import { Tabs } from "@components/Tabs" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + +In this tutorial, you'll learn how to use Chainlink Data Streams with the _[Streams Direct](/data-streams#streams-direct-using-data-streams-with-your-own-bot) implementation_ and the [REST API](/data-streams/reference/streams-direct-interface-api) to fetch reports from the Data Streams Aggregation Network. You'll set up your Go project, retrieve reports from the API, decode them, and log their attributes to your terminal. + + + +## Before you begin + +- **Go Version**: Make sure you have Go version 1.21 or higher. You can check your current version by running `go version` in your terminal and download the latest version from the official [Go website](https://go.dev/) if necessary. +- **API Credentials**: Access to the Streams Direct implementation requires API credentials. If you haven't already, [contact us](https://chainlinkcommunity.typeform.com/datastreams?#ref_id=docs) to talk to an expert about integrating Chainlink Data Streams with your applications. + +## Tutorial + +You'll start with the set up of your Go project. Next, you'll fetch and decode reports for both single and multiple feeds, and log their attributes to your terminal. + +### Set up your Go project + +1. Create and navigate to your project directory: + + ```bash + mkdir data-streams-direct && cd data-streams-direct + ``` + +1. Initialize a Go module: + + ```bash + go mod init data-streams-direct + ``` + +1. Open the `go.mod` file at the root of your project directory and include the necessary module and package information: + + + +1. Create `client` and `internal` subfolders within your project directory: + + ```bash + mkdir client internal + ``` + +1. Create a `client.go` file in the `client` subfolder and a `decoder.go` file in the `internal` subfolder: + + ```bash + touch client/client.go + touch internal/decoder.go + ``` + +1. Insert the provided code into `client.go` and `decoder.go` to enable your application to fetch and decode reports: + + + client/client.go + internal/decoder.go + + + + + + + + +1. Execute the command below to download dependencies and generate the `go.sum` file: + + ```bash + go mod tidy + ``` + + Your project directory should now have the following structure: + + ```plaintext + data-streams-direct/ + ├── client/ + │ └── client.go + ├── go.mod + ├── go.sum + ├── internal/ + │ └── decoder.go + ``` + +### Set environment variables + +Set the required environment variables in your terminal session to authenticate with the Data Streams Aggregation Network API: + + ```bash + export BASE_URL="api.testnet-dataengine.chain.link" + export CLIENT_ID="YOUR_CLIENT_ID" + export CLIENT_SECRET="YOUR_CLIENT_SECRET" + ``` + + - `BASE_URL` is the REST endpoint to poll for specific reports. See the [Streams Direct Interface](/data-streams/reference/streams-direct-interface-api#domains) guide for more information. + - Replace `CLIENT_ID` and `CLIENT_SECRET` with your API credentials. + +### Fetch and decode a report with a single feed + +1. Create `main.go` at the root of your project directory: + + ```bash + touch main.go + ``` + +1. Open `main.go` and insert the following code to fetch and decode a single feed: + + + +1. For this example, you will read from the ETH/USD Data Streams feed on Arbitrum Sepolia. This feed ID is . See the [Data Streams Feed IDs](/data-streams/stream-ids) page for a complete list of available assets. + + Execute your application: + + ```bash + go run main.go 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 + ``` + + Expect output similar to the following in your terminal: + + ``` + Generating HMAC with the following: GET /api/v1/reports?feedID=0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782×tamp=1713947821 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1713947821602 + base: api.testnet-dataengine.chain.link + header: map[Authorization:[] X-Authorization-Signature-Sha256:[7ba6d89a7fd5af48a51e54859fe61bfe5205385ea59d7fc2ad1636e9ed1dba92] X-Authorization-Timestamp:[1713947821602]] + params: map[feedID:[0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782] timestamp:[1713947821]] + + Report Details + ============== + Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 + + Decoded V3 Report Details: + ------------------------- + Valid From Timestamp: 1713947821 + Observations Timestamp: 1713947821 + Native Fee: 30697637229100 + Link Fee: 6468225201489100 + Expires At: 1714034221 + Benchmark Price: 3257579704051546000000 + Bid: 3257546265080495200000 + Ask: 3257633285580761400000 + + + Payload for onchain verification + ========================================= + Payload (hexadecimal): 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff15780000000000000000000000000000000000000000000000000000000020a8f908000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782000000000000000000000000000000000000000000000000000000006628c4ad000000000000000000000000000000000000000000000000000000006628c4ad00000000000000000000000000000000000000000000000000001beb59c3322c0000000000000000000000000000000000000000000000000016fad1097644cc00000000000000000000000000000000000000000000000000000000662a162d0000000000000000000000000000000000000000000000b097fff9701a850a800000000000000000000000000000000000000000000000b097892cdef361d7000000000000000000000000000000000000000000000000b098be558e09ed02c0000000000000000000000000000000000000000000000000000000000000000262810ea0ddf1d0883c6bab3cc10215ad7babd96fee6abd62b66f1cf8d8ef88c12dbae561312990e0a03945df9baf01d599354232d422772bb4fecc563baa96a500000000000000000000000000000000000000000000000000000000000000023a8e6710120b441f06d7475c2e207867f53cb4d0fb7387880109b3a2192b1b4027cce218afeeb5b2b2110a9bfac8e4a976f5e2c5e11e08afceafda9a8e13aa99 + ------------------------------------------------ + ``` + +#### Decoded report details + +The decoded report details include: + +| Attribute | Value | Description | +| ---------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Feed ID | `0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782` | The unique identifier for the Data Streams feed. In this example, the feed is ETH/USD. | +| Valid From Timestamp | `1713947821` | The start validity timestamp for the report, indicating when the data becomes relevant. | +| Observations Timestamp | `1713947821` | The timestamp indicating when the data was captured. | +| Native Fee | `30697637229100` | The fee to pay in the native blockchain token (e.g., testnet ETH on Arbitrum Sepolia) for the onchain verification of the report data. With 18 decimals. **Note:** This example fee is not indicative of actual fees. | +| Link Fee | `6468225201489100` | The fee to pay in LINK tokens for the onchain verification of the report data. With 18 decimals. For readability: `0.0064682252014891` LINK. **Note:** This example fee is not indicative of actual fees. | +| Expires At | `1714034221` | The expiration timestamp of the report, indicating the point at which the data becomes outdated. | +| Benchmark Price | `3257579704051546000000` | The observed price in the report, with 18 decimals. For readability: `3,257.579704051546` ETH per USD. | +| Bid | `3257546265080495200000` | The simulated price impact of a buy order up to the X% depth of liquidity usage. For readability: `3,257.5462650804952` ETH per USD. | +| Ask | `3257633285580761400000` | The simulated price impact of a sell order up to the X% depth of liquidity usage. For readability: `3,257.6332855807614` ETH per USD. | + +#### Payload for onchain verification + +In this tutorial, you log and decode the `FullReport` payload to extract the report data. In a +production environment, you should verify the data onchain to ensure its integrity and authenticity. Refer to the +[Verify report data onchain](/data-streams/tutorials/streams-direct-onchain-verification) guide. + +### Fetch and decode a report with multiple feeds + +1. Open your `main.go` file at the root of your project directory. + +1. Replace the `main.go` file content with the following code to fetch and decode multiple feeds: + + + +1. Ensure the required environment variables are still defined in your terminal session. + + ```bash + echo $BASE_URL + echo $CLIENT_ID + echo $CLIENT_SECRET + ``` + + Otherwise, [set them](/data-streams/tutorials/streams-direct-api#set-environment-variables) again. + +1. For this example, you will read from the ETH/USD and LINK/USD Data Streams feeds on Arbitrum Sepolia. Run your application: + + ```bash + go run main.go 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265 + ``` + + Expect to see the output below in your terminal: + + ``` + Generating HMAC with the following: GET /api/v1/reports/bulk?feedIDs=0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782%2C0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265×tamp=1713947384 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 YOUR_CLIENT_ID 1713947385462 + base: api.testnet-dataengine.chain.link + header: map[Authorization:[] X-Authorization-Signature-Sha256:[833a935aa45887b412ad7be1a88e8845c79333f04a6c777d9bd9cc245892e36a] X-Authorization-Timestamp:[1713947385462]] + params: map[feedIDs:[0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782,0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265] timestamp:[1713947384]] + + Payload for onchain verification for Feed ID 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 + ============================================================= + Payload (hexadecimal): 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff15780000000000000000000000000000000000000000000000000000000020a8b312000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782000000000000000000000000000000000000000000000000000000006628c2f8000000000000000000000000000000000000000000000000000000006628c2f800000000000000000000000000000000000000000000000000001beedfc3915c0000000000000000000000000000000000000000000000000016ef912bff40e800000000000000000000000000000000000000000000000000000000662a14780000000000000000000000000000000000000000000000b081b97fcea69480000000000000000000000000000000000000000000000000b0805ec11fd48080000000000000000000000000000000000000000000000000b083d9972145f8bae000000000000000000000000000000000000000000000000000000000000000023e4f63204f352f3a0d39a734c65fc535838226e7963d7180154b97d47b7684202bba4519d029ccb2458dac09ff546d5ddf6387ab00011001698ae9368723e83c0000000000000000000000000000000000000000000000000000000000000002604ab9834eaf5fd3c0ea835aa70716a167f07faf55018310b1cf9bd90e3c2a881a3753e271c19dc9ef6481ef52a2df64dbb2992aa640926de3d8d42cb4866348 + ------------------------------------------------------------- + + + Payload for onchain verification for Feed ID 0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265 + ============================================================= + Payload (hexadecimal): 0x00060a2676459d14176b64106fcf3246631d3a03734171737eb082fe79c956e000000000000000000000000000000000000000000000000000000000254fb30f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002800001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265000000000000000000000000000000000000000000000000000000006628c2f8000000000000000000000000000000000000000000000000000000006628c2f800000000000000000000000000000000000000000000000000001beedea3b4640000000000000000000000000000000000000000000000000016ef462a08af7c00000000000000000000000000000000000000000000000000000000662a1478000000000000000000000000000000000000000000000000d6f99cfc1ffe9000000000000000000000000000000000000000000000000000d6f63cf703753000000000000000000000000000000000000000000000000000d6fcfd013c87f00000000000000000000000000000000000000000000000000000000000000000029e96ae3827e09874b89a13b19b1bc0fa8797b233ad25b45bd7866899c55ad463b3c3d3cfbb51c9fcbd42c86e4b0fc808c829753931c8586919257dbd87afdd5500000000000000000000000000000000000000000000000000000000000000026eab928bf633bd0edb6a517f275363c52f25a073f371e1288a290cbd2a9f51693528559572c2974a80462672c35e5e9ac44409c2e883da6bf19fe32e56166900 + ------------------------------------------------------------- + + + Report Details + ============== + Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 + Decoded V3 Report Details: + ------------------------- + Valid From Timestamp: 1713947384 + Observations Timestamp: 1713947384 + Native Fee: 30712770302300 + Link Fee: 6455856275079400 + Expires At: 1714033784 + Benchmark Price: 3255974600000000000000 + Bid: 3255877000000000000000 + Ask: 3256127748030959500000 + ------------------------------------------------ + Feed ID: 0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265 + Decoded V3 Report Details: + ------------------------- + Valid From Timestamp: 1713947384 + Observations Timestamp: 1713947384 + Native Fee: 30712751436900 + Link Fee: 6455534119595900 + Expires At: 1714033784 + Benchmark Price: 15490585000000000000 + Bid: 15489635000000000000 + Ask: 15491535000000000000 + ------------------------------------------------ + ``` + + Your application has successfully fetched and decoded data for both feeds. + +#### Payload for onchain verification + +In this tutorial, you log and decode the `FullReport` payloads to extract the report data. In a +production environment, you should verify the data onchain to ensure its integrity and authenticity. Refer to the +[Verify report data onchain](/data-streams/tutorials/streams-direct-onchain-verification) guide. + +## Explanation + +### Fetching a report + +The application fetches a report from the Data Streams Aggregation Network using either the `FetchSingleReportSingleFeed` function for a single feed or the `FetchSingleReportMultipleFeeds` function for multiple feeds, found in the `client` package. This step involves sending an API request and receiving a response with the report data in either a `SingleReport` struct (single feed) or a `BulkReportResponse` struct (multiple feeds). + +The response includes a `FullReport` blob containing the encoded report's context and observations. + +### Decoding a report + +When the application successfully fetches the report, it decodes the report with the `DecodeFullReportAndReportData` function from the `internal` package. The process includes: + +1. **`FullReport` blob decoding**: It first decodes the `FullReport` blob into a `FullReport` struct, extracting the report context (including the feed ID(s) and the report version) and the report data. In this example, the feed uses the `V3` report version. + +1. **Report data decoding**: Based on the feed ID(s) version(s), it transforms the raw data into a structured format. In this example, the application decodes the report data into a `V3Report` struct, which contains the report's attributes. + +### Handling the decoded data + +The application logs the structured report data to the terminal. However, this data can be used for further processing, analysis, or display in your own application. diff --git a/src/content/data-streams/tutorials/streams-direct-onchain-verification.mdx b/src/content/data-streams/tutorials/streams-direct-onchain-verification.mdx new file mode 100644 index 00000000000..f643b726561 --- /dev/null +++ b/src/content/data-streams/tutorials/streams-direct-onchain-verification.mdx @@ -0,0 +1,143 @@ +--- +section: dataStreams +date: Last Modified +title: "Verify report data onchain" +whatsnext: { "Find the list of available Data Streams Feed IDs": "/data-streams/stream-ids" } +--- + +import { CodeSample, CopyText, ClickToZoom } from "@components" +import { Tabs } from "@components/Tabs" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + +In this tutorial, you'll learn how to verify onchain the integrity of reports by confirming their authenticity as signed by the Decentralized Oracle Network (DON). You'll use a verifier contract to verify the data onchain and pay the verification fee in LINK tokens. + + + +## Before you begin + +Make sure you understand how to use the [Streams Direct](/data-streams#streams-direct-using-data-streams-with-your-own-bot) implementation of Chainlink Data Streams to fetch reports via the [REST API](/data-streams/reference/streams-direct-interface-api) or [WebSocket](/data-streams/reference/streams-direct-interface-ws) connection. Refer to the following guides for more information: + +- [Fetch and decode reports via a REST API](/data-streams/tutorials/streams-direct-api) +- [Stream and decode reports via WebSocket](/data-streams/tutorials/streams-direct-ws) + +## Requirements + +- This guide requires testnet ETH and LINK on _Arbitrum Sepolia_. Both are available at [faucets.chain.link](https://faucets.chain.link/arbitrum-sepolia). +- Learn how to [Fund your contract with LINK](/resources/fund-your-contract). + +## Tutorial + +### Deploy the verifier contract + +Deploy a `ClientReportsVerifier` contract on _Arbitrum Sepolia_. This contract is enabled to verify reports and pay the verification fee in LINK tokens. + +1. [Open the ClientReportsVerifier.sol](https://remix.ethereum.org/#url=https://docs.chain.link/samples/DataStreams/ClientReportsVerifier.sol) contract in Remix. + + + +1. Select the `ClientReportsVerifier.sol` contract in the **Solidity Compiler** tab. + + + +1. Compile the contract. + +1. Open MetaMask and set the network to _Arbitrum Sepolia_. If you need to add Arbitrum Sepolia to your wallet, you can find the chain ID and the LINK token contract address on the [LINK Token Contracts](/resources/link-token-contracts#arbitrum-sepolia-testnet) page. + + - + Arbitrum Sepolia testnet and LINK token contract + + +1. On the **Deploy & Run Transactions** tab in Remix, select _Injected Provider - MetaMask_ in the **Environment** list. Remix will use the MetaMask wallet to communicate with _Arbitrum Sepolia_. + + + +1. In the **Contract** section, select the `ClientReportsVerifier` contract and fill in the **verifier proxy address** corresponding to the Data Streams feed you want to read from. You can find this address on the [Data Streams Feed IDs](/data-streams/stream-ids) page. The verifier proxy address for the ETH/USD feed on Arbitrum Sepolia is . + + + +1. Click the **Deploy** button to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to ensure you deploy the contract to _Arbitrum Sepolia_. + +1. After you confirm the transaction, the contract address appears under the **Deployed Contracts** list in Remix. Save this contract address for later. + + + +### Fund the verifier contract + +In this example, the verifier contract pays for onchain verification of reports in LINK tokens. + +Open MetaMask and send 1 testnet LINK on _Arbitrum Sepolia_ to the verifier contract address you saved earlier. + +### Verify a report onchain + +1. In Remix, on the **Deploy & Run Transactions** tab, expand your verifier contract under the **Deployed Contracts** section. + +1. Fill in the `verifyReport` function input parameter with the report payload you want to verify. You can use the following full report payload obtained in the [Fetch and decode report via a REST API](/data-streams/tutorials/streams-direct-api) guide: + + ``` + 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff15780000000000000000000000000000000000000000000000000000000020a8f908000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782000000000000000000000000000000000000000000000000000000006628c4ad000000000000000000000000000000000000000000000000000000006628c4ad00000000000000000000000000000000000000000000000000001beb59c3322c0000000000000000000000000000000000000000000000000016fad1097644cc00000000000000000000000000000000000000000000000000000000662a162d0000000000000000000000000000000000000000000000b097fff9701a850a800000000000000000000000000000000000000000000000b097892cdef361d7000000000000000000000000000000000000000000000000b098be558e09ed02c0000000000000000000000000000000000000000000000000000000000000000262810ea0ddf1d0883c6bab3cc10215ad7babd96fee6abd62b66f1cf8d8ef88c12dbae561312990e0a03945df9baf01d599354232d422772bb4fecc563baa96a500000000000000000000000000000000000000000000000000000000000000023a8e6710120b441f06d7475c2e207867f53cb4d0fb7387880109b3a2192b1b4027cce218afeeb5b2b2110a9bfac8e4a976f5e2c5e11e08afceafda9a8e13aa99 + ``` + + {" "} + + +1. Click the `verifyReport` button to call the function. MetaMask prompts you to accept the transaction. + +1. Click the `last_decoded_price` getter function to view the decoded price from the verified report. The answer on the ETH/USD feed uses 18 decimal places, so an answer of `3257579704051546000000` indicates an ETH/USD price of 3,257.579704051546. Each Data Streams feed uses a different number of decimal places for answers. See the [Data Streams Feed IDs](/data-streams/stream-ids) page for more information. + + + +## Examine the code + +The example code you deployed has all the interfaces and functions required to verify Data Streams reports onchain. + + + +### Initializing the contract + +When deploying the contract, you define the verifier proxy address for the Data Streams feed you want to read from. You can find this address on the [Data Streams Feed IDs](/data-streams/stream-ids) page. The verifier proxy address provides functions that are required for this example: + +- The `s_feeManager` function to estimate the verification fees. +- The `verify` function to verify the report onchain. + +### Verifying a report + +The `verifyReport` function is the main function of the contract that verifies the report onchain. It follows the steps below: + +- Fee calculation: It interacts with the `FeeManager` contract, accessible via the verifier proxy contract, to determine the fees associated with verifying the report. + +- Token approval: It grants approval to the `rewardManager` contract to spend the required amount of LINK tokens from its balance. + +- Report verification: The core verification step involves calling the `verify` function from the verifier proxy contract. This function takes the (unverified) report payload and the encoded fee token address as inputs and returns the verified report data. + +- Data decoding and storage: In this example, the verified report data is decoded into a `Report` struct, extracting the report data. The extracted price data is then emitted through the `DecodedPrice` event and stored in the `last_decoded_price` state variable. diff --git a/src/content/data-streams/tutorials/streams-direct-ws.mdx b/src/content/data-streams/tutorials/streams-direct-ws.mdx new file mode 100644 index 00000000000..f9dce5b2fe3 --- /dev/null +++ b/src/content/data-streams/tutorials/streams-direct-ws.mdx @@ -0,0 +1,236 @@ +--- +section: dataStreams +date: Last Modified +title: "Stream and decode reports via WebSocket" +whatsnext: + { + "Learn how to verify your data onchain": "/data-streams/reference/streams-direct-onchain-verification", + "Find the list of available Data Streams Feed IDs": "/data-streams/stream-ids", + } +--- + +import { CodeSample, CopyText } from "@components" +import { Tabs } from "@components/Tabs" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + +In this tutorial, you'll learn how to use Chainlink Data Streams with the _[Streams Direct](/data-streams#streams-direct-using-data-streams-with-your-own-bot) implementation_ and a [WebSocket connection](/data-streams/reference/streams-direct-interface-ws). You'll set up your Go project, listen for real-time reports from the Data Streams Aggregation Network, decode the report data, and log their attributes to your terminal. + + + +## Before you begin + +- **Go Version**: Make sure you have Go version 1.21 or higher. You can check your current version by running `go version` in your terminal and download the latest version from the official [Go website](https://go.dev/) if necessary. +- **API Credentials**: Access to the Streams Direct implementation requires API credentials. If you haven't already, [contact us](https://chainlinkcommunity.typeform.com/datastreams?#ref_id=docs) to talk to an expert about integrating Chainlink Data Streams with your applications. + +## Tutorial + +### Set up your Go project + +1. Create and navigate to your project directory: + + ```bash + mkdir data-streams-direct-ws && cd data-streams-direct-ws + ``` + +1. Initialize a Go module: + + ```bash + go mod init data-streams-direct-ws + ``` + +1. Open the `go.mod` file at the root of your project directory and include the necessary module and package information: + + + +1. Create `client` and `internal` subfolders within your project directory: + + ```bash + mkdir client internal + ``` + +1. Create a `clientWs.go` file in the `client` subfolder and a `decoder.go` file in the `internal` subfolder: + + ```bash + touch client/clientWs.go + touch internal/decoder.go + ``` + +1. Insert the provided code into `clientWs.go` and `decoder.go` to allow your application to establish a WebSocket connection, listen for new reports, and decode them: + + + client/clientWs.go + internal/decoder.go + + + + + + + + +1. Execute the command below to download dependencies and generate the `go.sum` file: + + ```bash + go mod tidy + ``` + + Your project directory should now have the following structure: + + ```plaintext + data-streams-direct-ws/ + ├── client/ + │ └── clientWs.go + ├── go.mod + ├── go.sum + ├── internal/ + │ └── decoder.go + ``` + +### Set environment variables + +Set the required environment variables in your terminal session to authenticate with the Data Streams Aggregation Network: + + ```bash + export BASE_URL="ws.testnet-dataengine.chain.link" + export CLIENT_ID="YOUR_CLIENT_ID" + export CLIENT_SECRET="YOUR_CLIENT_SECRET" + ``` + + - `BASE_URL` is the WebSocket endpoint to subscribe to price updates. See the [Streams Direct Interface](/data-streams/reference/streams-direct-interface-ws#domains) guide for more information. + - Replace `CLIENT_ID` and `CLIENT_SECRET` with your API credentials. + +### Establish a WebSocket connection and listen for real-time reports + +1. Create `main.go` at the root of your project directory: + + ```bash + touch main.go + ``` + +1. Open `main.go` and insert the following code to establish a WebSocket connection and listen for real-time data reports from the Data Streams Aggregation Network: + + + +1. For this example, you will read from the ETH/USD Data Streams feed on Arbitrum Sepolia. This feed ID is . See the [Data Streams Feed IDs](/data-streams/stream-ids) page for a complete list of available assets. + + Launch the WebSocket listener by running the following command in your terminal: + + ```bash + go run main.go 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 + ``` + + Expect output similar to the following in your terminal: + + ``` + Generating HMAC with the following: GET /api/v1/ws?feedIDs=0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1712557137856 + Received the following message: {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c540000000000000000000000000000000000000000000000000000000066138c5400000000000000000000000000000000000000000000000000001a8d2d99af3c0000000000000000000000000000000000000000000000000014193c2106c39c000000000000000000000000000000000000000000000000000000006614ddd40000000000000000000000000000000000000000000000b9b0fee093d4178d800000000000000000000000000000000000000000000000b9b0c6cc20c60f80400000000000000000000000000000000000000000000000b9b136f506e21f9ac00000000000000000000000000000000000000000000000000000000000000002cbfff26b44d58842991f29cd912099b90672f032fbca13b4d35b870c176b50b60396640cc31466f16bcd09a1afe01597fe194870af103d7d4f5c77648358559e000000000000000000000000000000000000000000000000000000000000000232408446b215b7e0ee343144575cb39c63f694ba162ac127336996e1f8b428547a2ce48e6acee6bcb3cab020f92c9bdf3d2fd5fd8e3b832d5c2267bbe5a3a633}} + Received the following message: {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c550000000000000000000000000000000000000000000000000000000066138c5500000000000000000000000000000000000000000000000000001a8d2d80abb80000000000000000000000000000000000000000000000000014193b117c4380000000000000000000000000000000000000000000000000000000006614ddd50000000000000000000000000000000000000000000000b9b0ff8f82d63faa400000000000000000000000000000000000000000000000b9b0c9ce73c00f5a000000000000000000000000000000000000000000000000b9b1355091ec6ced4000000000000000000000000000000000000000000000000000000000000000028b26f5ce5210ba244241da391b852d53bb97d76b561c752f12d147c0d0e07565294f878ac10b4f8f94940374339965d3cb1f7194b0d7303a081c26bdd3f4706e000000000000000000000000000000000000000000000000000000000000000278f418c832ad07cab0522e623875c0f5b4c4359cb0ad76c571dbaa02de699ec172b5e9bd4bfc7e2d6667301555434bd01db348112666c264891c59874272c520}} + Received the following message: {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c560000000000000000000000000000000000000000000000000000000066138c5600000000000000000000000000000000000000000000000000001a8cefac07b000000000000000000000000000000000000000000000000000141937477f35e0000000000000000000000000000000000000000000000000000000006614ddd60000000000000000000000000000000000000000000000b9b2afff39fd7358400000000000000000000000000000000000000000000000b9b293198c5103b0000000000000000000000000000000000000000000000000b9b2cce4e7a9e300800000000000000000000000000000000000000000000000000000000000000002753b3027be86222709eff93f98059486c278e442535b0c53127908dab09826f22e4fd9d1148550720de61813bd294e3084d724f3d83d972d5abe684a6d9d9ce900000000000000000000000000000000000000000000000000000000000000024236585d1840548b88d2dab324ecb414fbb898f6a9dc4a80979b8f9460802955389e98a95dd26ffc9813489dd2747b6394f95934034ebe1e47fa822bb7419b13}} + Received the following message: {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c570000000000000000000000000000000000000000000000000000000066138c5700000000000000000000000000000000000000000000000000001a8cef5e26300000000000000000000000000000000000000000000000000014191020cfc724000000000000000000000000000000000000000000000000000000006614ddd70000000000000000000000000000000000000000000000b9b2b21fed369590400000000000000000000000000000000000000000000000b9b28e01befceded800000000000000000000000000000000000000000000000b9b2d381feb661a9a00000000000000000000000000000000000000000000000000000000000000002c99d39f03b2e1dc88597401992d0ab2a2ebb829725a7aff4a5b2312dd9d79ccf0447a62620c088d68d577c63029cad7bd46e354775e9d5e1ddb30db3854bb9380000000000000000000000000000000000000000000000000000000000000002158b93031523cda28c976edd47c4b9b6500c13e65026422be3e623de4251fca45ed0107225c022c1a3480cdebc0cb86d5365bc8538702d13d7d80934c12a3275}} + Received the following message: {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c580000000000000000000000000000000000000000000000000000000066138c5800000000000000000000000000000000000000000000000000001a8cf081ff1400000000000000000000000000000000000000000000000000141946a6b6b830000000000000000000000000000000000000000000000000000000006614ddd80000000000000000000000000000000000000000000000b9b2aa26b724837a200000000000000000000000000000000000000000000000b9ae56d1e39f64f0000000000000000000000000000000000000000000000000b9b39e26a94459d6a00000000000000000000000000000000000000000000000000000000000000002c633d7ac6c69851ef98d890df1b4ba2d31d9a44fcb4e7d40ed91a6a088cf66c58f194573f0ff92f72d87c49fbb07637a35591a7d5c0746666415a46d6a5300f800000000000000000000000000000000000000000000000000000000000000027be80f9819c263925579e18bae6660cd509f4723ffa2417efe9bcc659045af021e8dad3f288a6e166e1bc43fe8c0e0910becdc395f1ff09602ebec10da2dd120}} + 2024/04/08 08:19:04 Websocket: Sending ping... + Received the following message: {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49ab03000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c590000000000000000000000000000000000000000000000000000000066138c5900000000000000000000000000000000000000000000000000001a8cf0bc3f300000000000000000000000000000000000000000000000000014194ce58b2658000000000000000000000000000000000000000000000000000000006614ddd90000000000000000000000000000000000000000000000b9b2a88f4e6b50f2e00000000000000000000000000000000000000000000000b9aeb43cd0a5cd7ec00000000000000000000000000000000000000000000000b9b4d02e31df9123400000000000000000000000000000000000000000000000000000000000000002de39c78c325f14159a4dc9797ccafb0d18a5ec278cd13f5ebc43c3988ce27d304d5174161b14636fcfac22bc06ea83fefd630e6ff826dc5e8ee5fed38adb42f800000000000000000000000000000000000000000000000000000000000000021537709709f273854856f78e0c36acf6580eb87cb23798ff0f8d5fd14b14beed7439265bdd11aca9d9a7f981251fc995c687750cb324d0a61b1e70155d0075b1}} + 2024/04/08 08:19:05 Websocket: Received pong... + Received the following message: {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49ab07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c5a0000000000000000000000000000000000000000000000000000000066138c5a00000000000000000000000000000000000000000000000000001a8cf593f46c000000000000000000000000000000000000000000000000001419360d1b077c000000000000000000000000000000000000000000000000000000006614ddda0000000000000000000000000000000000000000000000b9b286b0a60b5f3ca00000000000000000000000000000000000000000000000b9ae8a4a15117180000000000000000000000000000000000000000000000000b9b4eabe86f969652000000000000000000000000000000000000000000000000000000000000000023ede508913fc4066c754edba91d60e12c71911d616231da453e27eba07f34538dc3f32ae21d6ab44cff5fbba64b240f38252ee0a483c6450518257dc748926e900000000000000000000000000000000000000000000000000000000000000021650e1dc132ec7d7ef19672d3ae8c4e0bd1a069439d017664bb665d29a3b415163c6aa6172b2fb6e1658dee2e09292b741fc81cd17cce224e6e790fe3749df2f}} + ``` + + When you examine a message from the WebSocket connection's output, you notice that it contains two values: + + ``` + {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49ab07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c5a0000000000000000000000000000000000000000000000000000000066138c5a00000000000000000000000000000000000000000000000000001a8cf593f46c000000000000000000000000000000000000000000000000001419360d1b077c000000000000000000000000000000000000000000000000000000006614ddda0000000000000000000000000000000000000000000000b9b286b0a60b5f3ca00000000000000000000000000000000000000000000000b9ae8a4a15117180000000000000000000000000000000000000000000000000b9b4eabe86f969652000000000000000000000000000000000000000000000000000000000000000023ede508913fc4066c754edba91d60e12c71911d616231da453e27eba07f34538dc3f32ae21d6ab44cff5fbba64b240f38252ee0a483c6450518257dc748926e900000000000000000000000000000000000000000000000000000000000000021650e1dc132ec7d7ef19672d3ae8c4e0bd1a069439d017664bb665d29a3b415163c6aa6172b2fb6e1658dee2e09292b741fc81cd17cce224e6e790fe3749df2f}} + ``` + + - The first value `0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782` is the `feedId`. In this example, it is the ETH/USD feed on Arbitrum Sepolia. + - The second value is the `FullReport` payload in hexadecimal. It contains the encoded report data, which you'll decode in the next section. Save the `FullReport` payload value for the next step. + +**Note**: In this example, you listen to a single feed. You can listen to multiple feeds by providing the feed IDs as command line arguments separated by spaces. For example: + +```bash +go run main.go 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265 +``` + +### Decode the reports + +1. Create `decodeFullReportHex.go` at the root of your project directory: + + ```bash + touch decodeFullReportHex.go + ``` + +1. - Open `decodeFullReportHex.go` and insert the sample code provided below. It uses the `DecodeFullReportAndReportData` function from the `internal` package to decode the `FullReport` part of the message. + - Replace the `fullReportHex` variable value with the hexadecimal value you saved earlier from your WebSocket message output, without the `0x` prefix. + + {" "} + + + +1. Run the decoding script by executing the following command in your terminal: + + ```bash + go run decodeFullReportHex.go + ``` + + The `decodedReport` contains structured data about the report, including `FeedId` and `FeedVersion`. + + Expect output similar to the following in your terminal: + + ``` + Decoded Report: &{FeedId:0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 FeedVersion:3 V2Report: V3Report:0x140001b4c60 Round:11 Epoch:1919476 Digest:[0 6 249 181 83 227 147 206 211 17 85 30 253 48 209 222 206 219 99 215 106 212 23 55 70 46 44 219 189 255 21 120]} + Valid From Timestamp: 1712557606 + Observations Timestamp: 1712557606 + Native Fee: 29191712479600 + Link Fee: 5655191262392300 + Expires At: 1712644006 + Benchmark Price: 3425629793731062400000 + Bid: 3425455500000000000000 + Ask: 3425747100000000000000 + ``` + +#### Decoded report details + +The decoded report details include: + +| Attribute | Value | Description | +| ---------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Feed ID | `0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782` | The unique identifier for the Data Streams feed. In this example, the feed is ETH/USD on Arbitrum Sepolia with the Basic Report schema. | +| Feed Version | `3` | The feed report schema version. | +| Valid From Timestamp | `1712557606` | The start validity timestamp for the report, indicating when the data becomes relevant. | +| Observations Timestamp | `1712557606` | The timestamp indicating when the data was captured. | +| Native Fee | `29191712479600` | The fee to pay in the native blockchain token (e.g., testnet ETH on Arbitrum Sepolia) for the onchain verification of the report data. With 18 decimals. **Note:** This example fee is not indicative of actual fees. | +| Link Fee | `5655191262392300` | The fee to pay in LINK tokens for the onchain verification of the report data. With 18 decimals. For readability: `0.005655191262` LINK. **Note:** This example fee is not indicative of actual fees. | +| Expires At | `1712644006` | The expiration timestamp of the report, indicating the point at which the data becomes outdated. | +| Benchmark Price | `3425629793731062400000` | The observed price in the report, with 18 decimals. For readability: `3,425.6297937310624` ETH per USD. | +| Bid | `3425455500000000000000` | The simulated price impact of a buy order up to the X% depth of liquidity usage. For readability: `3,425.4555` ETH per USD. | +| Ask | `3425747100000000000000` | The simulated price impact of a sell order up to the X% depth of liquidity usage. For readability: `3,425.7471` ETH per USD. | + +### Verify report data onchain + +In this tutorial, you log and decode the `FullReport` payloads to extract the reports data. In a +production environment, you should verify the data onchain to ensure its integrity and authenticity. Refer to the +[Verify report data onchain](/data-streams/tutorials/streams-direct-onchain-verification) guide. + +## Explanation + +### Establishing a WebSocket connection and listening for message reports + +The `ConnectAndListen` function in the `client` package initializes the connection and enables the application to listen for messages from the Data Streams Aggregation Network via WebSocket. + +- Authentication and security: Uses HMAC headers, created with `CLIENT_ID` and `CLIENT_SECRET`, for secure, authenticated communication. +- Continuous listening: After authentication, continuously listens for real-time report messages, which include the `feedId` and the encoded `FullReport`. + +### Decoding a report + +The `DecodeFullReportAndReportData` function in the `internal` package unpacks the `FullReport` payload, decodes the `FullReport`'s `ReportBlob`, and decodes the report data into a structured format: + +1. It unpacks the `FullReport` payload that includes the report context, the report blob and raw signatures: + + - Report context: The report context contains metadata such as the `feedId` and the report version. + - Cryptographic signatures: These signatures validate the data's integrity and authenticity. + +1. Report data decoding: The function proceeds to decode the report data into a structured format. + +### Handling the decoded data + +In this example, the application logs the structured report data to the terminal. However, this data can be used for further processing, analysis, or display in your own application. diff --git a/src/features/data-streams/common/DataStreams.astro b/src/features/data-streams/common/DataStreams.astro index ac759fce8da..50a3aef7f33 100644 --- a/src/features/data-streams/common/DataStreams.astro +++ b/src/features/data-streams/common/DataStreams.astro @@ -3,15 +3,20 @@ const GettingStarted = await Astro.glob("./gettingStarted.mdx") const GettingStartedComponent = GettingStarted[0].Content const GettingStartedHardhat = await Astro.glob("./gettingStartedHardhat.mdx") const GettingStartedHardhatComponent = GettingStartedHardhat[0].Content + +const AsideDisclaimer = await Astro.glob("./asideDisclaimer.mdx") +const AsideDisclaimerComponent = AsideDisclaimer[0].Content + const DsNotes = await Astro.glob("./dsNotes.mdx") const DsNotesComponent = DsNotes[0].Content export type Props = { - section?: "gettingStarted" | "gettingStartedHardhat" | "dsNotes" + section?: "gettingStarted" | "gettingStartedHardhat" | "asideDisclaimer" | "dsNotes" } const { section } = Astro.props as Props --- {section === "gettingStarted" && } {section === "gettingStartedHardhat" && } +{section === "asideDisclaimer" && } {section === "dsNotes" && } diff --git a/src/features/data-streams/common/asideDisclaimer.mdx b/src/features/data-streams/common/asideDisclaimer.mdx new file mode 100644 index 00000000000..b1cd8b771ca --- /dev/null +++ b/src/features/data-streams/common/asideDisclaimer.mdx @@ -0,0 +1,11 @@ +import { Aside } from "@components" + + diff --git a/src/features/data-streams/common/asideOnChainVerification.mdx b/src/features/data-streams/common/asideOnChainVerification.mdx new file mode 100644 index 00000000000..646e6cf726a --- /dev/null +++ b/src/features/data-streams/common/asideOnChainVerification.mdx @@ -0,0 +1,6 @@ +import { Aside } from "@components" + + diff --git a/src/features/data-streams/common/gettingStarted.mdx b/src/features/data-streams/common/gettingStarted.mdx index 0a2fe1dc426..33c40b7f93f 100644 --- a/src/features/data-streams/common/gettingStarted.mdx +++ b/src/features/data-streams/common/gettingStarted.mdx @@ -1,18 +1,13 @@ -import { Aside, CodeSample, CopyText, ClickToZoom } from "@components" +import { CodeSample, CopyText, ClickToZoom } from "@components" +import DataStreams from "@features/data-streams/common/DataStreams.astro" This guide shows you how to read data from a Data Streams feed, validate the answer, and store the answer onchain. This guide uses the [Remix IDE](https://remix-project.org/) so you can complete these steps in a web-based development environment. If you prefer to complete these steps using terminal commands, read the [Getting Started - Hardhat CLI](/data-streams/getting-started-hardhat) guide instead. -This example uses a [Chainlink Automation Log Trigger](/chainlink-automation/guides/log-trigger) to check for events that require data. For this example, the log trigger comes from a simple emitter contract. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, return the data in a callback, and run the [`performUpkeep` function](/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. +This example uses the _[Streams Trade](/data-streams#streams-trade-using-data-streams-with-chainlink-automation) implementation_, with a [Chainlink Automation Log Trigger](/chainlink-automation/guides/log-trigger) to check for events that require data. For this example, the log trigger comes from a simple emitter contract. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, return the data in a callback, and run the [`performUpkeep` function](/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. - +Note: To learn how to use the _[Streams Direct](/data-streams#streams-direct-using-data-streams-with-your-own-bot) implementation_ of Data Streams, see the [Fetch and decode reports via a REST API](/data-streams/tutorials/streams-direct-api) guide or the [Stream and decode reports via WebSocket](/data-streams/tutorials/streams-direct-ws) guide. + + ## Before you begin @@ -20,16 +15,14 @@ This example uses a [Chainlink Automation Log Trigger](/chainlink-automation/gui - The [Solidity](https://soliditylang.org/) programming language - The [MetaMask](https://metamask.io) wallet - The [Remix](https://remix.ethereum.org/) development environment -- Acquire testnet funds. This guide requires testnet ETH and LINK on _Arbitrum Sepolia_. - - Use the [Arbitrum Bridge](https://bridge.arbitrum.io/) to transfer testnet ETH from Ethereum Sepolia to Arbitrum Sepolia. Testnet ETH on Ethereum Sepolia is available at one of [several faucets](https://faucetlink.to/sepolia). - - Testnet LINK is available for Arbitrum Sepolia at [faucets.chain.link](https://faucets.chain.link/arbitrum-sepolia). +- This guide requires testnet ETH and LINK on _Arbitrum Sepolia_. Both are available at [faucets.chain.link](https://faucets.chain.link/arbitrum-sepolia). - Learn how to [Fund your contract with LINK](/resources/fund-your-contract). ## Tutorial ### Deploy the Chainlink Automation upkeep contract -Deploy an upkeep contract that is enabled to retrieve data from Data Streams. For this example, you will read from the ETH/USD feed with ID `0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782` on Arbitrum Sepolia. See the [Stream Identifiers](/data-streams/stream-ids) page for a complete list of available assets, IDs, and verifier proxy addresses. +Deploy an upkeep contract that is enabled to retrieve data from Data Streams. For this example, you will read from the ETH/USD Data Streams feed on Arbitrum Sepolia. This feed ID is . See the [Data Streams Feed IDs](/data-streams/stream-ids) page for a complete list of available assets. 1. [Open the StreamsUpkeep.sol](https://remix.ethereum.org/#url=https://docs.chain.link/samples/DataStreams/StreamsUpkeep.sol) contract in Remix. @@ -63,7 +56,7 @@ Deploy an upkeep contract that is enabled to retrieve data from Data Streams. Fo style="max-width: 70%;" /> -1. In the **Contract** section, select the `StreamsUpkeep` contract and fill in the **verifier proxy address** corresponding to the stream you want to read from. You can find this address on the [Stream IDs](/data-streams/stream-ids) page. The verifier proxy address for the ETH/USD feed on Arbitrum Sepolia is . +1. In the **Contract** section, select the `StreamsUpkeep` contract and fill in the **verifier proxy address** corresponding to the Data Streams feed you want to read from. You can find this address on the [Data Streams Feed IDs](/data-streams/stream-ids) page. The verifier proxy address for the ETH/USD feed on Arbitrum Sepolia is . @@ -218,6 +213,7 @@ After registering your upkeep contract with Chainlink Automation with a log trig 1. The emitted log triggers the Chainlink Automation upkeep. 2. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, returns the data in a callback (`checkCallback`), and runs the `performUpkeep` function on your registered upkeep contract. + 2. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, returns the data in a callback (`checkCallback`), and runs the `performUpkeep` function on your registered upkeep contract. 3. The `performUpkeep` function calls the `verify` function on the verifier contract to verify the report onchain. 4. In this example, the `performUpkeep` function also stores the price from the report in the `last_retrieved_price` state variable and emits a `PriceUpdate` log message with the price. @@ -227,6 +223,6 @@ The `last_retrieved_price` getter function of your upkeep contract retrieves the ### Optional: Handle Data Streams fetching errors offchain with `checkErrorHandler` -When Automation detects the triggering event, it runs the `checkLog` function of your upkeep contract, which includes a `StreamsLookup` revert custom error. The `StreamsLookup` revert enables your upkeep to fetch a report from Data Streams. If the report is fetched successfully, the `checkCallback` function is evaluated offchain. Otherwise, the `checkErrorHandler` function is evaluated offchain to determine what Automation should do next. +When Automation detects the triggering event, it runs the `checkLog` function of your upkeep contract, which includes a `StreamsLookup` revert custom error. The `StreamsLookup` revert enables your upkeep to fetch a report from the Data Streams Aggregation Network. If the report is fetched successfully, the `checkCallback` function is evaluated offchain. Otherwise, the `checkErrorHandler` function is evaluated offchain to determine what Automation should do next. In this example, the `checkErrorHandler` is set to always return `true` for `upkeepNeeded`. This implies that the upkeep is always triggered, even if the report fetching fails. You can modify the `checkErrorHandler` function to handle errors offchain in a way that works for your specific use case. Read more about [using the StreamsLookup error handler](/chainlink-automation/guides/streams-lookup-error-handler). diff --git a/src/features/data-streams/common/gettingStartedHardhat.mdx b/src/features/data-streams/common/gettingStartedHardhat.mdx index d51481f6f94..ff6cf3718a5 100644 --- a/src/features/data-streams/common/gettingStartedHardhat.mdx +++ b/src/features/data-streams/common/gettingStartedHardhat.mdx @@ -1,23 +1,11 @@ import { Aside, CodeSample, CopyText } from "@components" import DataStreams from "@features/data-streams/common/DataStreams.astro" -This guide shows you how to read data from a Data Streams feed, verify the answer onchain, and store it. This CLI guide uses the [Hardhat Framework](https://hardhat.org/) so you can complete these steps using terminal commands rather than the web-based Remix IDE. If you prefer Remix or are unfamiliar with how to run terminal commands, read the [Getting Started - Remix IDE](/data-streams/getting-started) guide instead. +This guide shows you how to read data from a Data Streams feed, verify the answer onchain, and store it. This CLI guide uses the [Hardhat](https://hardhat.org/) development environment so you can complete these steps using terminal commands rather than the web-based Remix IDE. If you prefer Remix or are unfamiliar with how to run terminal commands, read the [Getting Started - Remix IDE](/data-streams/getting-started) guide instead. -This example uses a [Chainlink Automation Log Trigger](/chainlink-automation/guides/log-trigger) to check for events that require data. The flow follows this sequence: +This example uses the _[Streams Trade](/data-streams#streams-trade-using-data-streams-with-chainlink-automation) implementation_, with a [Chainlink Automation Log Trigger](/chainlink-automation/guides/log-trigger) to check for events that require data. For this example, the log trigger comes from a simple emitter contract. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, return the data in a callback, and run the [`performUpkeep` function](/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. -- A simple emitter contract emits a log that triggers the upkeep. -- Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, returns the data in a callback, and runs the [`performUpkeep` function](/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 and stores the retrieved price onchain. - - +Note: If you want to learn how to use the _[Streams Direct](/data-streams#streams-direct-using-data-streams-with-your-own-bot) implementation_ of Data Streams, refer to the [Fetch and decode reports via a REST API](/data-streams/tutorials/streams-direct-api) guide or the [Stream and decode reports via WebSocket](/data-streams/tutorials/streams-direct-ws) guide. @@ -28,7 +16,7 @@ This guide uses the [Hardhat](https://hardhat.org/) development environment to d ### Requirements - **Git**: Make sure you have Git installed. You can check your current version by running 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 . To ensure you are running the correct version in a terminal, type . +- **Nodejs** and **npm**: [Install the latest release of Node.js 20](https://nodejs.org/en/download/). Optionally, you can use [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md) to switch between installed Node.js versions with . To ensure you are running the correct version in a terminal, type . ```bash $ node -v v20.11.0 @@ -39,20 +27,21 @@ This guide uses the [Hardhat](https://hardhat.org/) development environment to d ### Setup -1. Clone the repository that contains the Hardhat project setup for this guide. This repository contains the Solidity contracts and the Hardhat configuration files you need to deploy and interact with the contracts. +1. Clone the [repository](https://github.com/smartcontractkit/smart-contract-examples) that contains the Hardhat project setup for this guide. This repository contains the Solidity contracts and the Hardhat configuration files you need to deploy and interact with the contracts. ```bash git clone https://github.com/smartcontractkit/smart-contract-examples.git cd smart-contract-examples/data-streams/getting-started/hardhat + cd smart-contract-examples/data-streams/getting-started/hardhat ``` -1. Install all the dependencies: +1. Install the dependencies: ```bash npm install ``` -1. Set an encryption password for your environment variables. This password needs to be set each time you create or restart a terminal shell session. +1. Set an encryption password for your environment variables. This password needs to be set each time you create or restart a terminal session. ```bash npx env-enc set-pw @@ -158,7 +147,7 @@ Expect output similar to the following in your terminal: ✔ Last Retrieved Price: 2945878120219995000000 ``` -The answer on the ETH/USD feed uses 18 decimal places, so an answer of `2945878120219995000000` indicates an ETH/USD price of 2945.878120219995. Each Data Streams feed uses a different number of decimal places for answers. See the [Data Streams Feed IDs](/data-streams/stream-ids) page for more information. +The answer on the ETH/USD feed uses 18 decimal places, so an answer of `2945878120219995000000` indicates an ETH/USD price of 2,945.878120219995. Each Data Streams feed uses a different number of decimal places for answers. See the [Data Streams Feed IDs](/data-streams/stream-ids) page for more information. Alternatively, you can view the price emitted in the logs for your upkeep transaction. @@ -166,7 +155,9 @@ You can find the upkeep transaction hash at [Chainlink Automation UI](https://au ## Examine the code -The example code you deployed has all the interfaces and functions required to work with Chainlink Automation as an upkeep contract. It follows a similar flow to the trading flow in the [Architecture](/data-streams#example-trading-flow) documentation but uses a basic log emitter to simulate the client contract that would initiate a `StreamsLookup`. The code example uses `revert` with `StreamsLookup` to convey call information about what streams to retrieve. See the [EIP-3668 rationale](https://eips.ethereum.org/EIPS/eip-3668#rationale) for more information about how to use `revert` in this way. +The example code you deployed has all the interfaces and functions required to work with Chainlink Automation as an upkeep contract. It follows a similar flow to the trading flow in the [Architecture](/data-streams/architecture#example-trading-flow-using-streams-trade) documentation but uses a basic log emitter to simulate the client contract that would initiate a `StreamsLookup`. After the contract receives and verifies the report, `performUpkeep` stores the price from the report in the `s_last_retrieved_price` and emits a `PriceUpdate` log message with the price. You could modify this to use the data in a way that works for your specific use case and application. + +The code example uses `revert` with `StreamsLookup` to convey call information about what streams to retrieve. See the [EIP-3668 rationale](https://eips.ethereum.org/EIPS/eip-3668#rationale) for more information about how to use `revert` in this way. @@ -205,6 +196,7 @@ You can use the [`emitLog` task](https://github.com/smartcontractkit/smart-contr 1. The emitted log triggers the Chainlink Automation upkeep. 2. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, returns the data in a callback (`checkCallback`), and runs the `performUpkeep` function on your registered upkeep contract. + 2. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Aggregation Network, returns the data in a callback (`checkCallback`), and runs the `performUpkeep` function on your registered upkeep contract. 3. The `performUpkeep` function calls the `verify` function on the verifier contract to verify the report onchain. 4. In this example, the `performUpkeep` function also stores the price from the report in the `s_last_retrieved_price` state variable and emits a `PriceUpdate` log message with the price. @@ -214,6 +206,6 @@ The [`getLastRetrievedPrice`](https://github.com/smartcontractkit/smart-contract ### Optional: Handle Data Streams fetching errors offchain with `checkErrorHandler` -When Automation detects the triggering event, it runs the `checkLog` function of your upkeep contract, which includes a `StreamsLookup` revert custom error. The `StreamsLookup` revert enables your upkeep to fetch a report from Data Streams. If the report is fetched successfully, the `checkCallback` function is evaluated offchain. Otherwise, the `checkErrorHandler` function is evaluated offchain to determine what Automation should do next. +When Automation detects the triggering event, it runs the `checkLog` function of your upkeep contract, which includes a `StreamsLookup` revert custom error. The `StreamsLookup` revert enables your upkeep to fetch a report from the Data Streams Aggregation Network. If the report is fetched successfully, the `checkCallback` function is evaluated offchain. Otherwise, the `checkErrorHandler` function is evaluated offchain to determine what Automation should do next. In this example, the `checkErrorHandler` is set to always return `true` for `upkeepNeeded`. This implies that the upkeep is always triggered, even if the report fetching fails. You can modify the `checkErrorHandler` function to handle errors offchain in a way that works for your specific use case. Read more about [using the StreamsLookup error handler](/chainlink-automation/guides/streams-lookup-error-handler).