Fetch and decode Data Streams reports using the TypeScript SDK
Guide Versions
This guide is available in multiple versions. Choose the one that matches your needs.
In this tutorial, you'll learn how to use the Data Streams SDK for TypeScript to fetch and decode reports from the Data Streams Aggregation Network. You'll set up your TypeScript project, retrieve reports, decode them, and log their attributes.
Requirements
- Git: Make sure you have Git installed. You can check your current version by running
git --versionin your terminal and download the latest version from the official Git website if necessary. - Node.js: Make sure you have Node.js 20.0 or higher. You can check your current version by running
node --versionin your terminal and download the latest version from the official Node.js website if necessary. - TypeScript: Make sure you have TypeScript 5.3 or higher. You can check your current version by running
npx tsc --versionin your terminal and install or update TypeScript by runningnpm install -g typescriptif necessary. - API Credentials: Access to Data Streams requires API credentials. If you haven't already, contact us to request mainnet or testnet access.
Tutorial
You'll start with the set up of your TypeScript project, installing the SDK and pasting example code. This will let you decode reports for both single and multiple streams, logging their attributes to your terminal.
Set up your TypeScript project
-
Create a new directory for your project and navigate to it:
mkdir my-data-streams-project cd my-data-streams-project -
Initialize a new Node.js project:
npm init -y -
Install the TypeScript SDK and other required packages:
npm install @chainlink/data-streams-sdk dotenv npm install -D tsx -
Set your API credentials:
Option 1 - Environment variables:
export API_KEY="your_api_key_here" export USER_SECRET="your_user_secret_here"Option 2 -
.envfile:# Create .env file touch .env # Add your credentials API_KEY="your_api_key_here" USER_SECRET="your_user_secret_here"
Fetch and decode a report with a single stream
-
Create a new new TypeScript file,
singleStream.ts, in your project directory:touch singleStream.ts -
Insert the following code example and save your
singleStream.tsfile:import { createClient, decodeReport, LogLevel, getReportVersion, formatReport } from "@chainlink/data-streams-sdk" import "dotenv/config" async function main() { if (process.argv.length < 3) { console.error("Please provide a feed ID as an argument") console.error( "Example: npx tsx singleStream.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782" ) process.exit(1) } const feedId = process.argv[2] const version = getReportVersion(feedId) try { const config = { apiKey: process.env.API_KEY || "YOUR_API_KEY", userSecret: process.env.USER_SECRET || "YOUR_USER_SECRET", endpoint: "https://api.testnet-dataengine.chain.link", wsEndpoint: "wss://ws.testnet-dataengine.chain.link", // Comment to disable SDK logging: logging: { logger: console, logLevel: LogLevel.INFO, }, } const client = createClient(config) console.log(`\nFetching latest report for feed ${feedId} (${version})...\n`) // Get raw report data const report = await client.getLatestReport(feedId) console.log(`Raw Report Blob: ${report.fullReport}`) // Decode the report const decodedData = decodeReport(report.fullReport, report.feedID) // Combine decoded data with report metadata const decodedReport = { ...decodedData, feedID: report.feedID, validFromTimestamp: report.validFromTimestamp, observationsTimestamp: report.observationsTimestamp, } console.log(formatReport(decodedReport, version)) } catch (error) { if (error instanceof Error) { console.error("Error:", error.message) } else { console.error("Unknown error:", error) } process.exit(1) } } main() -
Read from a testnet crypto stream. The below example executes the application, reading from the
ETH/USDcrypto stream:npx tsx singleStream.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782Expect output similar to the following in your terminal:
Fetching latest report for feed 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 (V3)... [2025-09-23T00:09:49.042Z] [DataStreams] Request successful: GET https://api.testnet-dataengine.chain.link/api/v1/reports/latest?feedID=0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 - 200 Raw Report Blob: 0x00090d9e8d96765a0c49e03a6ae05c82e8f8de70cf179baa632f18313e54bd690000000000000000000000000000000000000000000000000000000001f6f486000000000000000000000000000000000000000000000000000000030000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000068d1e54c0000000000000000000000000000000000000000000000000000000068d1e54c00000000000000000000000000000000000000000000000000004539f757bc7c0000000000000000000000000000000000000000000000000034754304206ea30000000000000000000000000000000000000000000000000000000068f9724c0000000000000000000000000000000000000000000000e3e84d950bcd8d80000000000000000000000000000000000000000000000000e3e48f23626b5660000000000000000000000000000000000000000000000000e3eb7a12d8af1b00000000000000000000000000000000000000000000000000000000000000000002e7c71643e93efb8e759b1d1a8826579853d3aa2c96f59a2813e833a374c786f8f13a497a753af14c6b7329f704f148779b20aae62ed450167c61b9b5d8fcb0e100000000000000000000000000000000000000000000000000000000000000024905f1b4a6313246988a33d2aa923e3023065bbc8c874956aa5df25ec8b7b3e918a25b251713158aae6be85a188c1292e6b5288f2f96e75d632c4e6b659dfa53 Report Metadata: Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 Valid From: 1758586188 Observations: 1758586188 Decoded Data: Native Fee: 76115265174652 LINK Fee: 14765629481447075 Expires At: 1761178188 Price: 4204150104000000000000 Bid Price: 4203880326000000000000 Ask Price: 4204378800000000000000 --------------------------------------------------Your application has successfully fetched and decoded data for both streams.
Fetch and decode reports for multiple streams
-
Create a new TypeScript file,
multipleStreams.ts, in your project directory:touch multipleStreams.ts -
Insert the following code example in your
multipleStreams.tsfile:import { createClient, decodeReport, LogLevel, getReportVersion, formatReport } from "@chainlink/data-streams-sdk" import "dotenv/config" async function main() { if (process.argv.length < 3) { console.error("Please provide feed IDs as arguments") console.error("Get latest reports for multiple feeds:") console.error(" npx tsx multipleStreams.ts <feedID1> <feedID2> [feedID3...]") console.error("\nExample:") console.error( " npx tsx multipleStreams.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265" ) process.exit(1) } const feedIds = process.argv.slice(2) try { const config = { apiKey: process.env.API_KEY || "YOUR_API_KEY", userSecret: process.env.USER_SECRET || "YOUR_USER_SECRET", endpoint: "https://api.testnet-dataengine.chain.link", wsEndpoint: "wss://ws.testnet-dataengine.chain.link", // Comment to disable SDK logging: logging: { logger: console, logLevel: LogLevel.INFO, }, } const client = createClient(config) console.log(`\nFetching latest reports for ${feedIds.length} feed(s):`) feedIds.forEach((feedId) => { const version = getReportVersion(feedId) console.log(`- ${feedId} (${version})`) }) console.log() // Get latest reports for each feed ID const reports = [] for (const feedId of feedIds) { try { const report = await client.getLatestReport(feedId) reports.push(report) } catch (error) { console.error(`Failed to get report for ${feedId}:`, error) continue } } console.log(`Found ${reports.length} reports:\n`) // Process reports reports.forEach((report, index) => { const version = getReportVersion(report.feedID) console.log(`Raw Report Blob #${index + 1}: ${report.fullReport}`) try { // Decode the report const decodedData = decodeReport(report.fullReport, report.feedID) // Combine decoded data with report metadata const decodedReport = { ...decodedData, feedID: report.feedID, validFromTimestamp: report.validFromTimestamp, observationsTimestamp: report.observationsTimestamp, } console.log(formatReport(decodedReport, version)) } catch (error) { console.error(`Failed to decode report for ${report.feedID}:`, error) } }) } catch (error) { if (error instanceof Error) { console.error("Error:", error.message) } else { console.error("Unknown error:", error) } process.exit(1) } } main() -
Before running the example, verify that your API credentials are still set in your current terminal session:
echo $API_KEY echo $API_SECRETIf the commands above don't show your credentials, set them again:
export API_KEY="<YOUR_API_KEY>" export USER_SECRET="<YOUR_USER_SECRET>" -
Read from two testnet crypto streams (ETH/USD and LINK/USD) by running:
npx tsx multipleStreams.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265Expect to see the output below in your terminal:
[2025-09-24T01:50:28.313Z] [DataStreams] Data Streams client initialized Fetching latest report for feed 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 (V3)... [2025-09-24T01:50:28.607Z] [DataStreams] Request successful: GET https://api.testnet-dataengine.chain.link/api/v1/reports/latest?feedID=0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 - 200 Raw Report Blob: 0x00090d9e8d96765a0c49e03a6ae05c82e8f8de70cf179baa632f18313e54bd690000000000000000000000000000000000000000000000000000000001fb09db000000000000000000000000000000000000000000000000000000030000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000068d34e640000000000000000000000000000000000000000000000000000000068d34e64000000000000000000000000000000000000000000000000000045686a2caed300000000000000000000000000000000000000000000000000347613062ce6c40000000000000000000000000000000000000000000000000000000068fadb640000000000000000000000000000000000000000000000e34fc8e3afa8f400000000000000000000000000000000000000000000000000e34cf02d97047e60000000000000000000000000000000000000000000000000e3533bbd1e9ba3400000000000000000000000000000000000000000000000000000000000000000021ae965e613bfb4580ea819f8c12736562222e515b412054c49ec77ded163d9ee4493fb7dfb713181ea1a0d4e1a7fc4b7a3618484f9c989c4262c201e749df2cd000000000000000000000000000000000000000000000000000000000000000217c165a50d34910db8667c1229128ea4110fba082eb0308b75282fffdb594f68467797ce8a8b515f8e3ab08e4723a1fe1a9cfa5079968cd4bb7a9ec1a7a943cc Report Metadata: Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 Valid From: 1758678628 Observations: 1758678628 Decoded Data: Native Fee: 76314760228563 LINK Fee: 14766522869016260 Expires At: 1761270628 Price: 4193160000000000000000 Bid Price: 4192954886000000000000 Ask Price: 4193408500000000000000 --------------------------------------------------
Decoded report details
The decoded crypto v3 report details include:
| Attribute | Value | Description |
|---|---|---|
| Stream ID | 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 | The unique identifier for the stream. In this example, the stream is for ETH/USD. |
| Observations Timestamp | 1734216283 | The timestamp indicating when the data was captured. |
| Benchmark Price | 3865052126782320350000 | The observed price in the report, with 18 decimals. For readability: 3,865.0521267823204 USD per ETH. |
| Bid | 3864985478146740000000 | The highest price a buyer is willing to pay for an asset, with 18 decimals. For readability: 3,864.9854781467400 USD per ETH. Learn more about the Bid price. (For DEX State Price streams, this value equals Benchmark Price.) |
| Ask | 3865140837060103650000 | The lowest price a seller is willing to accept for an asset, with 18 decimals. For readability: 3,865.1408370601037 USD per ETH. Learn more about the Ask price. (For DEX State Price streams, this value equals Benchmark Price.) |
| Valid From Timestamp | 1734216283 | The start validity timestamp for the report, indicating when the data becomes relevant. |
| Expires At | 1734302683 | The expiration timestamp of the report, indicating the point at which the data becomes outdated. |
| Link Fee | 3379350941986000 | The fee to pay in LINK tokens for the onchain verification of the report data. With 18 decimals. For readability: 0.03379350941986 LINK. Note: This example fee is not indicative of actual fees. |
| Native Fee | 25872872271800 | The fee to pay in the native blockchain token (e.g., ETH on Ethereum) for the onchain verification of the report data. With 18 decimals. Note: This example fee is not indicative of actual fees. |
Payload for onchain verification
The fullReport field in the API response is the signed payload you pass directly to VerifierProxy.verify() (or verifyBulk()) for onchain verification. No transformation or re-encoding is needed — the raw bytes from the API are the input.
From the example output above, the fullReport for the ETH/USD stream looks like this:
0x00090d9e8d96765a0c49e03a6ae05c82e8f8de70cf179baa632f18313e54bd690000000000000000000000000000000000000000000000000000000001f6f486000000000000000000000000000000000000000000000000000000030000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000068d1e54c0000000000000000000000000000000000000000000000000000000068d1e54c00000000000000000000000000000000000000000000000000004539f757bc7c0000000000000000000000000000000000000000000000000034754304206ea30000000000000000000000000000000000000000000000000000000068f9724c0000000000000000000000000000000000000000000000e3e84d950bcd8d80000000000000000000000000000000000000000000000000e3e48f23626b5660000000000000000000000000000000000000000000000000e3eb7a12d8af1b00000000000000000000000000000000000000000000000000000000000000000002e7c71643e93efb8e759b1d1a8826579853d3aa2c96f59a2813e833a374c786f8f13a497a753af14c6b7329f704f148779b20aae62ed450167c61b9b5d8fcb0e100000000000000000000000000000000000000000000000000000000000000024905f1b4a6313246988a33d2aa923e3023065bbc8c874956aa5df25ec8b7b3e918a25b251713158aae6be85a188c1292e6b5288f2f96e75d632c4e6b659dfa53
This payload is ABI-encoded and contains the report header, the DON-signed report body, and the signatures used for onchain verification. You do not need to decode or inspect it before passing it to the Verifier — the VerifierProxy handles all of that.
In TypeScript, report.fullReport holds this value and can be passed directly to your verification contract:
// report.fullReport is the value to pass as `payload` to VerifierProxy.verify()
const decodedData = decodeReport(report.fullReport, report.feedID)
Refer to the Verify report data onchain tutorial for a step-by-step walkthrough of the full onchain verification flow.
Explanation
Initializing the client and configuration
The Data Streams TypeScript client is initialized in two steps:
- Configure the client with a config object:
const config = {
apiKey: process.env.API_KEY || "YOUR_API_KEY",
userSecret: process.env.USER_SECRET || "YOUR_USER_SECRET",
endpoint: "https://api.testnet-dataengine.chain.link",
wsEndpoint: "wss://ws.testnet-dataengine.chain.link",
// Optional logging:
logging: {
logger: console,
logLevel: LogLevel.INFO,
},
}
The configuration requires:
apiKeyanduserSecretfor authentication (required)endpointfor the API endpoint (required)loggingfor debugging and error tracking (optional)
See the SDK Reference page for more configuration options.
- Create the client with
createClient:
const client = createClient(config)
The client handles:
- Authentication with HMAC signatures
- Connection management and timeouts
- Error handling and retries
Fetching reports
The TypeScript SDK provides two main methods to fetch reports:
- Latest report for a single stream with
getLatestReport:
const report = await client.getLatestReport(feedId)
- Takes a feed ID string
- Returns a single
ReportResponsewith the most recent data - No timestamp parameter needed
- Useful for real-time price monitoring
- Latest reports for multiple streams by calling
getLatestReportin a loop:
for (const feedId of feedIds) {
const report = await client.getLatestReport(feedId)
reports.push(report)
}
- Takes an array of feed ID strings
- Calls
getLatestReportfor each feed ID individually - Returns the most recent data for each stream
- Useful for monitoring multiple assets simultaneously
- Each request gets the latest available data without timestamp constraints
Each API request automatically:
- Handles authentication with API credentials
- Manages request timeouts via the Node/SDK configuration
- Processes responses into structured types
Decoding reports
Reports are decoded in two steps using the TypeScript SDK helper functions:
- Report decoding with
decodeReport(auto-detects report version by feed ID):
const decodedData = decodeReport(report.fullReport, report.feedID)
This step:
- Takes the raw
fullReportbytes/string from the response - Uses the report schema that matches the feed (SDK detects version)
- Validates the format and decodes into a structured object
- Returns decoded data that can be combined with report metadata and formatted using
formatReport()
For more details, see the Report Format section of the SDK Reference.
Error handling
The TypeScript examples use standard try/catch patterns and optional timeouts:
- Request timeouts / cancellation
Use your application's timeout/cancellation mechanism (for example, AbortController) when making requests to the SDK or wrap calls in a manual timeout.
- Error checking
try {
const report = await client.getLatestReport(feedId)
} catch (err) {
console.error("Failed to fetch report:", err)
process.exit(1) // fatal error
}
- Fatal errors (client creation, missing credentials) typically exit the process
- Non-fatal errors (single report decode) can be logged and skipped when processing multiple feeds
- All errors should be logged with context for easier debugging
Learn more about SDK error handling in the SDK Reference.
- SDK logging
The SDK can log requests and responses when logging is enabled in the config. In the example above we pass console as the logger and set LogLevel.INFO.
The decoded data can be used for further processing or display in your application. For production environments, you must verify the data onchain using the provided fullReport payload.
For more information about SDK logging configuration and monitoring options, see the SDK Reference.
c98d3598 (draft ts-sdk-fetch)