Fetch and decode V10 reports for Backed xStock data using the Rust SDK

In this tutorial, you'll learn how to use the Data Streams SDK for Rust to fetch and decode V10 reports for Backed xStock streams from the Data Streams Aggregation Network. You'll set up your Rust project, retrieve reports, decode them, and log their attributes.

Requirements

  • Rust: Make sure you have Rust installed. You can install Rust by following the instructions on the official Rust website.
  • 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 Rust project. Next, you'll fetch and decode reports for Backed xStock streams and log their attributes to your terminal.

Set up your Rust 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 Rust project:

    cargo init
    
  3. Add the following dependencies to your Cargo.toml file:

    [dependencies]
    chainlink-data-streams-sdk = "1.0.3"
    chainlink-data-streams-report = "1.0.3"
    tokio = { version = "1.4", features = ["full"] }
    hex = "0.4"
    

Fetch and decode a report with a single stream

  1. Replace the contents of src/main.rs with the following code:

    use chainlink_data_streams_report::feed_id::ID;
    use chainlink_data_streams_report::report::{ decode_full_report, v10::ReportDataV10 };
    use chainlink_data_streams_sdk::client::Client;
    use chainlink_data_streams_sdk::config::Config;
    use std::env;
    use std::error::Error;
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn Error>> {
       // Get feed ID from command line arguments
       let args: Vec<String> = env::args().collect();
       if args.len() < 2 {
          eprintln!("Usage: cargo run [FeedID]");
          std::process::exit(1);
       }
       let feed_id_input = &args[1];
    
       // Get API credentials from environment variables
       let api_key = env::var("API_KEY").expect("API_KEY must be set");
       let api_secret = env::var("API_SECRET").expect("API_SECRET must be set");
    
       // Initialize the configuration
       let config = Config::new(
          api_key,
          api_secret,
          "https://api.testnet-dataengine.chain.link".to_string(),
          "wss://api.testnet-dataengine.chain.link/ws".to_string()
       ).build()?;
    
       // Initialize the client
       let client = Client::new(config)?;
    
       // Parse the feed ID
       let feed_id = ID::from_hex_str(feed_id_input)?;
    
       // Fetch the latest report
       let response = client.get_latest_report(feed_id).await?;
       println!("\nRaw report data: {:?}\n", response.report);
    
       // Decode the report
       let full_report = hex::decode(&response.report.full_report[2..])?;
       let (_report_context, report_blob) = decode_full_report(&full_report)?;
    let report_data = ReportDataV10::decode(&report_blob)?;
    
       // Print decoded report details
    println!("\nDecoded Report for Stream ID {}:", feed_id_input);
    println!("------------------------------------------");
    println!("Valid From Timestamp    : {}", response.report.valid_from_timestamp);
    println!("Observations Timestamp  : {}", response.report.observations_timestamp);
    println!("Expires At              : {}", report_data.expires_at);
    println!("Last Update Timestamp   : {}", report_data.last_update_timestamp);
    println!("Price                   : {}", report_data.price);
    println!("Native Fee              : {}", report_data.native_fee);
    println!("Link Fee                : {}", report_data.link_fee);
    println!("Market Status           : {}", report_data.market_status);
    println!("Current Multiplier      : {}", report_data.current_multiplier);
    println!("New Multiplier          : {}", report_data.new_multiplier);
    println!("Activation DateTime     : {}", report_data.activation_date_time);
    println!("Tokenized Price         : {}", report_data.tokenized_price);
    println!("------------------------------------------");
    
       Ok(())
    }
    
    
  2. Set up 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.

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

    Build and run your application. Replace [STREAM_ID] with your stream ID.

    cargo run -- [STREAM_ID]
    

    Expect output similar to the following in your terminal:

    Raw report data: Report { feed_id: [STREAM_ID], valid_from_timestamp: 1753790956, observations_timestamp: 1753790956, full_report: "0x..." }
    
    
    Decoded Report for Stream ID [STREAM_ID]:
    ------------------------------------------
    Valid From Timestamp    : 1753790956
    Observations Timestamp  : 1753790956
    Expires At              : 1756382956
    Last Update Timestamp   : 1753790956
    Price                   : 1234500000000000000000
    Native Fee              : 83650421200144
    Link Fee                : 17661691404993071
    Market Status           : 2
    Current Multiplier      : 1000000000000000000
    New Multiplier          : 0
    Activation DateTime     : 0
    Tokenized Price         : 0
    ------------------------------------------
    

Decoded report details

The decoded report details include:

FieldExample ValueDescription
feedId0x...Unique identifier for the Data Streams feed
validFromTimestamp1753790956Earliest timestamp when the price is valid
observationsTimestamp1753790956Latest timestamp when the price is valid
nativeFee83650421200144Cost to verify report onchain (native token)
linkFee17661691404993071Cost to verify report onchain (LINK)
expiresAt1756382956Expiration date of the report
lastUpdateTimestamp1753790956Timestamp of the last valid price update
price1234500000000000000000Last traded price from the real-world equity market
marketStatus2Status of the real-world equity market.
Possible values: 0 (Unknown), 1 (Closed), 2 (Open), 3 (Halted)
currentMultiplier1000000000000000000Currently applied multiplier accounting for past corporate actions
newMultiplier0Multiplier to be applied at the activationDateTime
(set to 0 if none is scheduled)
activationDateTime0When the next corporate action takes effect
(set to 0 if none is scheduled)
tokenizedPrice024/7 tokenized equity price as traded on supported exchanges
(In development; currently returns 0).

Payload for onchain verification

In this tutorial, you log and decode the full_report 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.

Explanation

Initializing the API client and configuration

The API client is initialized in two steps:

  1. Config::new creates a configuration with your API credentials and endpoints. This function:

    • Validates your API key and secret
    • Sets up the REST API endpoint for data retrieval
    • Configures optional settings like TLS verification
  2. Client::new creates the HTTP client with your configuration. This client:

    • Handles authentication automatically
    • Manages HTTP connections
    • Implements retry logic for failed requests

Fetching reports

The SDK provides several methods to fetch reports through the REST API:

  1. Latest report: get_latest_report retrieves the most recent report for a feed:

    • Takes a feed ID as input
    • Returns a single report with the latest timestamp
    • Useful for applications that need the most current data
  2. Historical report: get_report fetches a report at a specific timestamp:

    • Takes both feed ID and timestamp
    • Returns the report closest to the requested timestamp
    • Helpful for historical analysis or verification

Each API request automatically:

  • Generates HMAC authentication headers
  • Includes proper timestamps
  • Handles HTTP response status codes
  • Deserializes the JSON response into Rust structures

Decoding reports

Reports are decoded in three stages:

  1. Hex decoding: The full_report field comes as a hex string prefixed with "0x":

    let full_report = hex::decode(&response.report.full_report[2..])?;
    
  2. Report separation: decode_full_report splits the binary data:

    • Extracts the report context (metadata)
    • Isolates the report blob (actual data)
    • Validates the report format
  3. Data extraction: ReportDataV10::decode parses the report blob into structured data:

    • price: Last traded price from the real-world equity market
    • market_status: Status of the real-world equity market (0=Unknown, 1=Closed, 2=Open, 3=Halted)
    • current_multiplier: Currently applied multiplier accounting for past corporate actions
    • new_multiplier: Multiplier to be applied at the activation_date_time (set to 0 if none is scheduled)
    • activation_date_time: When the next corporate action takes effect (set to 0 if none is scheduled)
    • tokenized_price: 24/7 tokenized equity price as traded on supported exchanges (currently always returns 0)

Error handling

The example demonstrates Rust's robust error handling:

  1. Type-safe errors:

    • Uses custom error types for different failure scenarios
    • Implements the Error trait for proper error propagation
    • Provides detailed error messages for debugging
  2. Error propagation:

    • Uses the ? operator for clean error handling
    • Converts errors between types when needed
    • Bubbles up errors to the main function
  3. Input validation:

    • Checks command-line arguments
    • Validates environment variables
    • Verifies feed ID format

The decoded data can be used for further processing, analysis, or display in your application. For production environments, you must verify the data onchain using the provided full_report payload.

What's next

Get the latest Chainlink content straight to your inbox.