Indexing Data

Background

The indexer service extends the Data Anchor’s on-chain system to provide structured, long-term storage. It streams on-chain events and stores data blobs for efficient retrieval. By abstracting away low-level details like transaction signatures and slot numbers, it allows developers to work with higher-level, context-aware queries.

Rather than querying raw data such as “What happened in transaction X at slot 34,594?”, users can ask more intuitive questions like “How many proofs did Alice’s node submit in June?”

Naming Convention

To use the indexer effectively, users have to follow a predefined naming convention. This scheme isn’t enforced programmatically, but it’s required for the indexer to parse and categorize events correctly.

If a user’s account should be associated with a specific network, its namespace should conform to the pattern <user>.<network>—where the user name is a unique identifier within the network and the network functions like a domain. If a user can own multiple nodes in the same network, each one should set the user as a subdomain, such as:

  • <account_1>.<user>.<network> and <account_2>.<user>.<network>.

For example, in a network called “Cloud Network” where users can run multiple nodes, a contributor named Alice might set up one node from home and another from the office. In this case, her namespaces would be:

  • home.alice.cloud and office.alice.cloud, respectively.

This naming structure allows the indexer to group events by network and namespace, which allows for more intuitive queries and meaningful insights.

Getting Started

Once you've uploaded data to your blober PDA using the Data Anchor, you can use the indexer service to query data in an intuitive way beyond the ledger lifetime.

1

Configure Indexer Access

Please reach out to the team for an API key to access the indexer's endpoints and use the corresponding URLs for devnet and mainnet.

Network
RPC Endpoint

Devnet

https://devnet.indexer.data-anchor.termina.technology/

Mainnet

https://mainnet.indexer.data-anchor.termina.technology/

let indexer_url = "https://devnet.indexer.data-anchor.termina.technology/";
let data_anchor_client = DataAnchorClient::builder()
    .payer(payer)
    .program_id(program_id)
    .indexer_from_url(&indexer_url)
    .await?
    .build_with_config(config)
    .await?;

Add the indexer_url to the client builder configuration to enable indexer-related methods.

2

Query by Slot

Retrieve data uploaded to a specific account in a given slot by passing the account’s namespace and the corresponding slot number.

The namespace is the ASCII identifier used during initialization, and the slot number is returned during the upload_blob step.

let slot = your_upload_slot;
let blobs = data_anchor_client
    .get_blobs(slot, namespace.into())
    .await?;
3

Query by Blober PDA

Retrieve all data associated with your blober PDA across different time periods. This provides a complete view of all uploads to your namespace and supports optional time range filtering.

let blobs = data_anchor_client
    .get_blobs_by_blober(namespace.into(), None)
    .await?;

To query all blobs for the namespace without time restrictions, use None for the time range.

use chrono::{DateTime, Utc};

let start_time = DateTime::parse_from_rfc3339("2025-06-01T00:00:00Z")?.with_timezone(&Utc);
let end_time = DateTime::parse_from_rfc3339("2025-06-30T00:00:00Z")?.with_timezone(&Utc);
let time_range = Some((start_time, end_time));

let blobs = data_anchor_client
    .get_blobs_by_blober(namespace.into(), time_range)
    .await?;

To filter by time, create a time range tuple using RFC3339 timestamps.

4

Query by Network and Namespace

If the namespace follows the recommended naming convention (<user>.<network>), it's possible to use network-wide queries and user-specific filtering.

let network_name = "your_network";
let network_blobs = data_anchor_client
    .get_blobs_by_network(network_name.to_string(), time_range)
    .await?;

Use get_blobs_by_network to query all data that users have uploaded across the network.

let namespace = "user.network";
let payer_pubkey = Some(your_payer_pubkey);
let namespace_blobs = data_anchor_client
    .get_blobs_by_namespace_for_payer(namespace.into(), payer_pubkey, time_range)
    .await?;

Use get_blobs_by_namespace_for_payer for granular filtering by specific users.

The payer-pubkey parameter is optional to show only the blobs uploaded by that account in the namespace.

5

Query by Payer Address

Retrieve all data uploads paid for by a specific payer address within a network context. This is useful for tracking contributions from specific accounts or analyzing payment patterns.

let payer_pubkey = your_payer_pubkey;
let network_name = "your_network";
let payer_blobs = data_anchor_client
    .get_blobs_by_payer(payer_pubkey, network_name.to_string(), time_range)
    .await?;

Query all blobs paid for by a specific payer within a given network, with optional time range filtering.

6

Retrieve Proofs

The indexer provides cryptographic proofs that verify the authenticity of returned data. These proofs ensure data integrity and prove that the indexer hasn't tampered with the original uploads.

let slot = your_target_slot;
let slot_proof = data_anchor_client.get_slot_proof(slot, namespace.into()).await?;

Use get_slot_proof to generate a compound proof that verifies all blobs finalized at a specific slot within the namespace. This is efficient for verifying multiple blobs uploaded in the same transaction batch.

let blob_pubkey = your_blob_pubkey;
let blob_proof = data_anchor_client.get_proof_for_blob(blob_pubkey).await?;

Use get_proof_for_blob to generate a proof for a specific individual blob when you need granular verification.

7

Process Blob Metadata

The indexer returns structured data that includes your original blob data along with helpful metadata. Output formats vary between the Rust SDK (structured objects) and CLI utility (configurable text, JSON, or CSV formats).

let blobs = data_anchor_client.get_blobs(slot, namespace.into()).await?;

for blob in blobs {
    println!("Blob ID: {}", blob.id);
    println!("Slot: {}", blob.slot);
    println!("Timestamp: {}", blob.timestamp);
    println!("Data length: {} bytes", blob.data.len());
    
    // Your original uploaded data is ready to use
    let original_data = blob.data;
    // Process original_data as needed
}

The SDK returns structured objects with decoded data and metadata. Access your original data through the data field without any additional decoding.

Last updated