Fetch and decode V10 reports using the Go SDK

In this tutorial, you'll learn how to use the Data Streams SDK for Go to fetch and decode V10 reports for Backed xStock streams from the Data Streams Aggregation Network. You'll set up your Go 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 --version in your terminal and download the latest version from the official Git website if necessary.
  • 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 if 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 Go project. Next, you'll fetch and decode reports for both single and multiple Backed xStock streams, and log their attributes to your terminal.

Set up your Go project

  1. Create a new directory for your project and navigate to it:

    mkdir my-data-streams-project
    cd my-data-streams-project
    
  2. Initialize a new Go module:

    go mod init my-data-streams-project
    
  3. Install the Data Streams SDK:

    go get github.com/smartcontractkit/data-streams-sdk/go
    

Fetch and decode a report with a single stream

  1. Create a new Go file, single-stream.go, in your project directory:

    touch single-stream.go
    
  2. Insert the following code example and save your single-stream.go file:

    package main
    
    import (
       "context"
       "fmt"
       "os"
       "time"
    
       streams "github.com/smartcontractkit/data-streams-sdk/go"
       feed "github.com/smartcontractkit/data-streams-sdk/go/feed"
       report "github.com/smartcontractkit/data-streams-sdk/go/report"
       v10 "github.com/smartcontractkit/data-streams-sdk/go/report/v10" // Import the v10 report schema for Backed xStock streams.
    )
    
    func main() {
       // Validate command-line arguments
       if len(os.Args) < 2 {
          fmt.Printf("Usage: go run main.go [FeedID]\nExample: go run main.go [FEED_ID]\n")
          os.Exit(1)
       }
       feedIDInput := os.Args[1]
    
       // Get API credentials from environment variables
       apiKey := os.Getenv("API_KEY")
       apiSecret := os.Getenv("API_SECRET")
       if apiKey == "" || apiSecret == "" {
          fmt.Printf("API_KEY and API_SECRET environment variables must be set\n")
          os.Exit(1)
       }
    
       // Define the configuration for the SDK client
       cfg := streams.Config{
          ApiKey:    apiKey,
          ApiSecret: apiSecret,
          RestURL:   "https://api.testnet-dataengine.chain.link",
          Logger:    streams.LogPrintf,
       }
    
       // Initialize the SDK client
       client, err := streams.New(cfg)
       if err != nil {
          cfg.Logger("Failed to create client: %v\n", err)
          os.Exit(1)
       }
    
       // Create context with timeout
       ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
       defer cancel()
    
       // Parse the feed ID
       var feedID feed.ID
       if err := feedID.FromString(feedIDInput); err != nil {
          cfg.Logger("Invalid feed ID format '%s': %v\n", feedIDInput, err)
          os.Exit(1)
       }
    
       // Fetch the latest report
       reportResponse, err := client.GetLatestReport(ctx, feedID)
       if err != nil {
          cfg.Logger("Failed to get latest report: %v\n", err)
          os.Exit(1)
       }
    
       // Log the raw report data
       cfg.Logger("Raw report data: %+v\n", reportResponse)
    
       // Decode the report
       decodedReport, err := report.Decode[v10.Data](reportResponse.FullReport)
       if err != nil {
          cfg.Logger("Failed to decode report: %v\n", err)
          os.Exit(1)
       }
    
       // Format and display the decoded report
       fmt.Printf(
          "\nDecoded Report for Stream ID %s:\n"+
             "------------------------------------------\n"+
             "Valid From Timestamp    : %d\n"+
             "Observations Timestamp  : %d\n"+
             "Native Fee (wei)        : %s\n"+
             "Link Fee (wei)          : %s\n"+
             "Expires At              : %d\n"+
             "Last Update Timestamp   : %d\n"+
             "Price                   : %s\n"+
             "Market Status           : %d\n"+
             "Current Multiplier      : %s\n"+
             "New Multiplier          : %s\n"+
             "Activation DateTime     : %d\n"+
             "Tokenized Price         : %s\n"+
             "------------------------------------------\n",
          feedIDInput,
          decodedReport.Data.ValidFromTimestamp,
          decodedReport.Data.ObservationsTimestamp,
          decodedReport.Data.NativeFee.String(),
          decodedReport.Data.LinkFee.String(),
          decodedReport.Data.ExpiresAt,
          decodedReport.Data.LastUpdateTimestamp,
          decodedReport.Data.Price.String(),
          decodedReport.Data.MarketStatus,
          decodedReport.Data.CurrentMultiplier.String(),
          decodedReport.Data.NewMultiplier.String(),
          decodedReport.Data.ActivationDateTime,
          decodedReport.Data.TokenizedPrice.String(),
       )
    }
    
  3. Download the required dependencies and update the go.mod and go.sum files:

    go mod tidy
    
  4. Set up the SDK client configuration within single-stream.go with your API credentials and the REST endpoint:

    cfg := streams.Config{
       ApiKey:    os.Getenv("API_KEY"),
       ApiSecret: os.Getenv("API_SECRET"),
       RestURL:   "https://api.testnet-dataengine.chain.link",
       Logger: streams.LogPrintf,
    }
    
    • Set your API credentials as environment variables:
    export API_KEY="<YOUR_API_KEY>"
    export API_SECRET="<YOUR_API_SECRET>"
    

    Replace <YOUR_API_KEY> and <YOUR_API_SECRET> with your API credentials.

    • RestURL is the REST endpoint to poll for specific reports. See the Data Streams Interface page for more information.

    See the SDK Reference page for more configuration options.

  5. For this example, you will read from a Backed xStock stream. See the Data Streams Backed streams page for a complete list of available streams.

    Execute your application. Replace [STREAM_ID] with your stream ID.

    go run single-stream.go [STREAM_ID]
    

    Expect output similar to the following in your terminal:

    2025-08-15T11:29:46-07:00 Raw report data: {"fullReport":"0x...","feedID":"[STREAM_ID]","validFromTimestamp":1755282585,"observationsTimestamp":1755282585}
    
    Decoded Report for Stream ID [STREAM_ID]:
    ------------------------------------------
    Valid From Timestamp    : 1755282585
    Observations Timestamp  : 1755282585
    Native Fee (wei)        : 72390983702980
    Link Fee (wei)          : 14788098714955797
    Expires At              : 1757874585
    Last Update Timestamp   : 1755282581729000000
    Price                   : 329870000000000000000
    Market Status           : 2
    Current Multiplier      : 1000000000000000000
    New Multiplier          : 0
    Activation DateTime     : 0
    Tokenized Price         : 0
    ------------------------------------------
    

Decoded report details

The decoded report includes:

AttributeValueDescription
Stream ID[STREAM_ID]Unique identifier for the Data Streams feed.
Valid From Timestamp1755282585Earliest timestamp when the price is valid.
Observations Timestamp1755282585Latest timestamp when the price is valid.
Native Fee (wei)72390983702980Cost to verify report onchain (native token).
Link Fee (wei)14788098714955797Cost to verify report onchain (LINK).
Expires At1757874585Expiration date of the report.
Last Update Timestamp1755282581729000000Timestamp of the last valid price update.
Price329870000000000000000Last traded price from the real-world equity market (18 decimals). For readability: 329.87.
Market Status2Status of the real-world equity market. Possible values: 0 (Unknown), 1 (Closed), 2 (Open), 3 (Halted).
Current Multiplier1000000000000000000Currently applied multiplier accounting for past corporate actions (18 decimals). For readability: 1.0.
New Multiplier0Multiplier to be applied at the activationDateTime.
Activation DateTime0When the next corporate action takes effect (set to 0 if none is scheduled).
Tokenized Price024/7 tokenized equity price as traded on supported exchanges.

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 to ensure its integrity and authenticity. Refer to the Verify report data onchain tutorial.

Fetch and decode reports for multiple streams

  1. Create a new Go file, multiple-streams.go, in your project directory:

    touch multiple-streams.go
    
  2. Insert the following code example in your multiple-streams.go file:

    package main
    
    import (
    	"context"
    	"fmt"
    	"os"
    	"time"
    
    	streams "github.com/smartcontractkit/data-streams-sdk/go"
    	feed "github.com/smartcontractkit/data-streams-sdk/go/feed"
    	report "github.com/smartcontractkit/data-streams-sdk/go/report"
    	v10 "github.com/smartcontractkit/data-streams-sdk/go/report/v10" // Import the v10 report schema for Backed xStock streams
    )
    
    func main() {
    	// Validate command-line arguments
    	if len(os.Args) < 3 {
    		fmt.Printf("Usage: go run multiple-streams.go [StreamID1] [StreamID2] ...\n"+
    			"Example: go run multiple-streams.go [STREAM_ID_1] [STREAM_ID_2]\n")
    		os.Exit(1)
    	}
    
    	// Get API credentials from environment variables
    	apiKey := os.Getenv("API_KEY")
    	apiSecret := os.Getenv("API_SECRET")
    	if apiKey == "" || apiSecret == "" {
    		fmt.Printf("API_KEY and API_SECRET environment variables must be set\n")
    		os.Exit(1)
    	}
    
    	// Define the configuration for the SDK client
    	cfg := streams.Config{
    		ApiKey:    apiKey,
    		ApiSecret: apiSecret,
    		RestURL:   "https://api.testnet-dataengine.chain.link",
    		Logger:    streams.LogPrintf,
    	}
    
    	// Initialize the SDK client
    	client, err := streams.New(cfg)
    	if err != nil {
    		cfg.Logger("Failed to create client: %v\n", err)
    		os.Exit(1)
    	}
    
    	// Create context with timeout
    	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    	defer cancel()
    
    	// Parse Feed IDs
    	var ids []feed.ID
    	for _, arg := range os.Args[1:] {
    		var fid feed.ID
    		if err := fid.FromString(arg); err != nil {
    			cfg.Logger("Invalid feed ID format '%s': %v\n", arg, err)
    			continue
    		}
    		ids = append(ids, fid)
    	}
    
    	if len(ids) == 0 {
    		cfg.Logger("No valid feed IDs provided\n")
    		os.Exit(1)
    	}
    
    	// Fetch reports for all streams
    	timestamp := uint64(time.Now().Unix())
    	reportResponses, err := client.GetReports(ctx, ids, timestamp)
    	if err != nil {
    		cfg.Logger("Failed to get reports: %v\n", err)
    		os.Exit(1)
    	}
    
    	// Process reports
    	for _, reportResponse := range reportResponses {
    		// Log the raw report data
    		cfg.Logger("Raw report data for Stream ID %s: %+v\n",
    			reportResponse.FeedID.String(), reportResponse)
    
    		// Decode the report
    		decodedReport, err := report.Decode[v10.Data](reportResponse.FullReport)
    		if err != nil {
    			cfg.Logger("Failed to decode report for Stream ID %s: %v\n",
    				reportResponse.FeedID.String(), err)
    			continue // Skip to next report if decoding fails
    		}
    
    		// Format and display the decoded report
          fmt.Printf(
             "\nDecoded Report for Stream ID %s:\n"+
                "------------------------------------------\n"+
                "Valid From Timestamp    : %d\n"+
                "Observations Timestamp  : %d\n"+
                "Native Fee (wei)        : %s\n"+
                "Link Fee (wei)          : %s\n"+
                "Expires At              : %d\n"+
                "Last Update Timestamp   : %d\n"+
                "Price                   : %s\n"+
                "Market Status           : %d\n"+
                "Current Multiplier      : %s\n"+
                "New Multiplier          : %s\n"+
                "Activation DateTime     : %d\n"+
                "Tokenized Price         : %s\n"+
                "------------------------------------------\n",
             reportResponse.FeedID.String(),
             decodedReport.Data.ValidFromTimestamp,
             decodedReport.Data.ObservationsTimestamp,
             decodedReport.Data.NativeFee.String(),
             decodedReport.Data.LinkFee.String(),
             decodedReport.Data.ExpiresAt,
             decodedReport.Data.LastUpdateTimestamp,
             decodedReport.Data.Price.String(),
             decodedReport.Data.MarketStatus,
             decodedReport.Data.CurrentMultiplier.String(),
             decodedReport.Data.NewMultiplier.String(),
             decodedReport.Data.ActivationDateTime,
             decodedReport.Data.TokenizedPrice.String(),
    		)
    	}
    }
    
  3. Before running the example, verify that your API credentials are still set in your current terminal session:

    echo $API_KEY
    echo $API_SECRET
    

    If the commands above don't show your credentials, set them again:

    export API_KEY="<YOUR_API_KEY>"
    export API_SECRET="<YOUR_API_SECRET>"
    
  4. For this example, you will read from two Backed xStock streams. Run your application. Replace [STREAM_ID_1] and [STREAM_ID_2] with your stream IDs.

    go run multiple-streams.go [STREAM_ID_1] [STREAM_ID_2]
    

    Expect to see output similar to the following in your terminal:

       2024-12-14T17:53:30-05:00 Raw report data for Stream ID [STREAM_ID_1]: {"fullReport":"0x...","feedID":"[STREAM_ID_1]","usEquityPrice":123456,"tokenisedPrice":123456,"currentMultiplier":1.0,"marketStatus":"OPEN","stalenessTimestamp":1753551893,"reason":"dividend","activationTimestamp":1753551893,"newMultiplier":1.1}
    
       Decoded Report for Stream ID [STREAM_ID_1]:
       ------------------------------------------
       Valid From Timestamp    : 1755282585
       Observations Timestamp  : 1755282585
       Native Fee (wei)        : 72390983702980
       Link Fee (wei)          : 14788098714955797
       Expires At              : 1757874585
       Last Update Timestamp   : 1755282581729000000
       Price                   : 329870000000000000000
       Market Status           : 2
       Current Multiplier      : 1000000000000000000
       New Multiplier          : 0
       Activation DateTime     : 0
       Tokenized Price         : 0
       ------------------------------------------
       2024-12-14T17:53:30-05:00 Raw report data for Stream ID [STREAM_ID_2]: {"fullReport":"0x...","feedID":"[STREAM_ID_2]","validFromTimestamp":1755282585,"observationsTimestamp":1755282585,"nativeFee":72390983702980,"linkFee":14788098714955797,"expiresAt":1757874585,"lastUpdateTimestamp":1755282581729000000,"price":329870000000000000000,"marketStatus":2,"currentMultiplier":1000000000000000000,"newMultiplier":0,"activationDateTime":0,"tokenizedPrice":0}
    
       Decoded Report for Stream ID [STREAM_ID_2]:
       ------------------------------------------
       Valid From Timestamp    : 1755282585
       Observations Timestamp  : 1755282585
       Native Fee (wei)        : 72390983702980
       Link Fee (wei)          : 14788098714955797
       Expires At              : 1757874585
       Last Update Timestamp   : 1755282581729000000
       Price                   : 329870000000000000000
       Market Status           : 2
       Current Multiplier      : 1000000000000000000
       New Multiplier          : 0
       Activation DateTime     : 0
       Tokenized Price         : 0
       ------------------------------------------
    

    Your application has successfully fetched and decoded data for both streams.

Payload for onchain verification

In this guide, you log and decode the fullReport payloads to extract the report data. In a production environment, you should verify the data to ensure its integrity and authenticity. Refer to the Verify report data onchain guide.

Explanation

Initializing the client and configuration

The Data Streams client is initialized in two steps:

  1. Configure the client with streams.Config:

    cfg := streams.Config{
       ApiKey:    os.Getenv("API_KEY"),
       ApiSecret: os.Getenv("API_SECRET"),
       RestURL:   "https://api.testnet-dataengine.chain.link",
       Logger:    streams.LogPrintf,
    }
    

    The configuration requires:

    • ApiKey and ApiSecret for authentication (required)
    • RestURL for the API endpoint (required)
    • Logger for debugging and error tracking (optional, defaults to streams.LogPrintf)
  2. Create the client with streams.New:

    client, err := streams.New(cfg)
    

    The client handles:

    • Authentication with HMAC signatures
    • Connection management and timeouts
    • Error handling and retries

Fetching reports

The SDK provides two main methods to fetch reports:

  1. Latest report for a single stream with GetLatestReport:

    reportResponse, err := client.GetLatestReport(ctx, feedID)
    
    • Takes a context and feed ID
    • Returns a single ReportResponse with the most recent data
    • No timestamp parameter needed
    • Useful for real-time price monitoring
  2. Latest reports for multiple streams with GetReports:

    reportResponses, err := client.GetReports(ctx, ids, timestamp)
    
    • Takes context, feed IDs array, and Unix timestamp
    • Returns array of ReportResponse, one per feed ID
    • Timestamp determines the point in time for the reports
    • Efficient for monitoring multiple assets simultaneously

Each API request automatically:

  • Handles authentication with API credentials
  • Manages request timeouts via context
  • Processes responses into structured types

Decoding reports

Reports are decoded in two steps:

  1. Report decoding with report.Decode:

    decodedReport, err := report.Decode[v10.Data](reportResponse.FullReport)
    

    This step:

    • Takes the raw FullReport bytes from the response
    • Uses v10.Data schema for Backed xStock streams (use v3.Data for Crypto streams)
    • Validates the format and decodes using Go generics
    • Returns a structured report with typed data
  2. Data access:

    data := decodedReport.Data
    validFromTimestamp := data.ValidFromTimestamp
    observationsTimestamp := data.ObservationsTimestamp
    nativeFee := data.NativeFee.String()
    linkFee := data.LinkFee.String()
    expiresAt := data.ExpiresAt
    lastUpdateTimestamp := data.LastUpdateTimestamp
    price := data.Price.String()
    marketStatus := data.MarketStatus
    currentMultiplier := data.CurrentMultiplier.String()
    newMultiplier := data.NewMultiplier.String()
    activationDateTime := data.ActivationDateTime
    tokenizedPrice := data.TokenizedPrice.String()
    

    Provides access to:

    • Validity timestamps
    • Native and LINK fees
    • Expiry and last update timestamps
    • Price and tokenized price
    • Market status
    • Current and new multipliers
    • Activation date/time
    • All numeric values require .String() for display

Error handling

The SDK uses Go's standard error handling patterns with some enhancements:

  1. Context management:

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    • Sets request timeouts for API calls
    • defer cancel() ensures cleanup of resources
    • Same context pattern for both single and multiple reports
  2. Error checking:

    if err != nil {
       cfg.Logger("Failed to decode report: %v\n", err)
       os.Exit(1)           // Fatal errors: exit the program
       // or
       continue            // Non-fatal errors: skip this report
    }
    
    • Fatal errors (client creation, no valid feeds) use os.Exit(1)
    • Non-fatal errors (single report decode) use continue
    • All errors are logged before handling
  3. SDK logging:

    cfg.Logger("Raw report data: %+v\n", reportResponse)
    
    • Uses configured logger for SDK operations
    • fmt.Printf for user-facing output
    • Debug information includes raw report data
    • Structured error messages with context

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.

What's next

Get the latest Chainlink content straight to your inbox.