> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Data freshness monitoring

> Compare Solana Portal data freshness with external RPC providers.

The `solanaRpcLatencyWatcher` subscribes to RPC endpoints via WebSocket and measures when blocks arrive at the Portal versus when they appear at the RPC endpoints.

<Warning>
  The measured values include client-side network latency. For RPC endpoints, only the arrival time of blocks is measured. This does not capture the node's internal processing or response latency if queried directly. Results represent end-to-end delays as experienced by the client, not pure Portal or RPC processing performance.
</Warning>

Pass the watcher as the source output. You can chain a `.pipe()` transform to it, for example to expose the measurements as Prometheus metrics:

```ts theme={"system"}
import { formatBlock } from '@subsquid/pipes'
import { solanaPortalStream, solanaRpcLatencyWatcher } from '@subsquid/pipes/solana'
import { metricsServer } from '@subsquid/pipes/metrics/node'

async function main() {
  const stream = solanaPortalStream({
    id: 'solana-latency',
    portal: 'https://portal.sqd.dev/datasets/solana-mainnet',
    outputs: solanaRpcLatencyWatcher({
      rpcUrl: ['https://api.mainnet-beta.solana.com'], // RPC endpoints to monitor
    }).pipe({
      profiler: { name: 'expose metrics' },
      transform: (data, { metrics }) => {
        if (!data) return // Skip if no latency data

        // For each RPC endpoint, update the latency gauge metric
        for (const rpc of data.rpc) {
          metrics
            .gauge({
              name: 'rpc_latency_ms',
              help: 'RPC Latency in ms',
              labelNames: ['url'],
            })
            .set({ url: rpc.url }, rpc.portalDelayMs)
        }

        return data
      },
    }),

    metrics: metricsServer({ port: 9090 }),
  })

  // Iterate over the stream, logging block and RPC latency data
  for await (const { data } of stream) {
    if (!data) continue // Skip if no block data

    console.log(`Slot: ${formatBlock(data.number)} / ${data.timestamp.toString()}`)
    console.table(data.rpc)
  }
}

void main()
```

## Output format

Data freshness data includes:

* `number` / `timestamp`: the observed slot and its timestamp
* `portal.receivedAt`: when the block arrived from the Portal
* `rpc`: one entry per configured RPC endpoint:
  * `url`: RPC endpoint URL
  * `receivedAt`: when the RPC endpoint received the block
  * `hash`: block hash as seen by the RPC (not set on Solana)
  * `portalDelayMs`: milliseconds between RPC arrival and Portal availability

```
Slot: 369,377,455 / Fri Sep 26 2025 15:31:36 GMT+0400
┌───┬─────────────────────────────────────┬──────────────────────────┬───────────────┐
│   │ url                                 │ receivedAt               │ portalDelayMs │
├───┼─────────────────────────────────────┼──────────────────────────┼───────────────┤
│ 0 │ https://api.mainnet-beta.solana.com │ 2025-09-26T11:31:37.075Z │ 358           │
└───┴─────────────────────────────────────┴──────────────────────────┴───────────────┘
```

The Prometheus gauge registered in the `.pipe()` transform above is served on the `metricsServer()` port, `http://localhost:9090/metrics` in this example. See the [Metrics guide](./metrics) for details on custom metrics.


## Related topics

- [Data freshness monitoring](/en/sdk/pipes-sdk/evm/guides/advanced-topics/latency-monitoring.md)
- [solanaRpcLatencyWatcher](/en/sdk/pipes-sdk/solana/reference/utility-components/rpc-latency-watcher.md)
- [evmRpcLatencyWatcher](/en/sdk/pipes-sdk/evm/reference/utility-components/evm-rpc-latency-watcher.md)
- [Monitoring](/en/cloud/resources/monitoring.md)
- [Bitcoin quickstart](/en/sdk/pipes-sdk/bitcoin/quickstart.md)
