<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Usang Emmanuel </title>
    <description>The latest articles on DEV Community by Usang Emmanuel  (@emmanuellsensai).</description>
    <link>https://dev.to/emmanuellsensai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3878420%2F3f288475-6b16-4aa1-b1cf-0474031b792f.jpeg</url>
      <title>DEV Community: Usang Emmanuel </title>
      <link>https://dev.to/emmanuellsensai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/emmanuellsensai"/>
    <language>en</language>
    <item>
      <title>Proof server and Indexer: how Midnight processes transactions</title>
      <dc:creator>Usang Emmanuel </dc:creator>
      <pubDate>Tue, 21 Apr 2026 11:01:27 +0000</pubDate>
      <link>https://dev.to/emmanuellsensai/proof-server-and-indexer-how-midnight-processes-transactions-1adn</link>
      <guid>https://dev.to/emmanuellsensai/proof-server-and-indexer-how-midnight-processes-transactions-1adn</guid>
      <description>&lt;p&gt;&lt;em&gt;A hands-on walkthrough of Midnight's two core infrastructure components: the proof server that generates zero-knowledge proofs locally, and the Indexer that makes on-chain data queryable via GraphQL.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This tutorial is aimed at developers who are new to Midnight and want to understand how transactions are processed behind the scenes.&lt;/p&gt;

&lt;p&gt;When you build a DApp on Midnight, two pieces of infrastructure do most of the heavy work behind the scenes: the &lt;strong&gt;proof server&lt;/strong&gt; and the &lt;strong&gt;Indexer&lt;/strong&gt;. One handles the privacy side, the other handles the data side. You need both or nothing works. Understanding both is the difference between a DApp that works and a DApp that fails suddenly.&lt;/p&gt;

&lt;p&gt;The proof server is the reason your private data stays private on Midnight. It runs locally on your machine, takes the ZK circuits produced by your Compact contract, combines them with your private inputs, and gives out a zero-knowledge proof. That proof is what gets submitted on-chain, not your data.&lt;/p&gt;

&lt;p&gt;The Indexer handles the other direction. It watches the blockchain, parses every block and transaction, and exposes that data through a GraphQL API. Anything your DApp needs to read from on-chain contract state, transaction history, epoch info flows through the Indexer.&lt;/p&gt;

&lt;p&gt;In this tutorial we'll walk through what each component does, set them both up with Docker, talk about the version pinning that trips up most newcomers, and send real queries to the Indexer's GraphQL endpoint. By the end you'll have a working local stack and a mental model for how a Midnight transaction actually moves from your wallet to the chain and back, so walk with me, let's go.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faepz6vj9d1qlt50bbsoq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faepz6vj9d1qlt50bbsoq.png" alt="Midnight transaction lifecycle: from user action through proof server, node, and Indexer back to DApp" width="800" height="580"&gt;&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A machine running Ubuntu (or another Linux distribution)&lt;/li&gt;
&lt;li&gt;Docker installed and running&lt;/li&gt;
&lt;li&gt;Basic familiarity with the command line&lt;/li&gt;
&lt;li&gt;curl installed (for testing GraphQL queries)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the proof server actually does
&lt;/h2&gt;

&lt;p&gt;Midnight transactions are different from what you're used to on Ethereum or Solana. There's no signature in the usual sense. Instead, a transaction carries a &lt;strong&gt;zero-knowledge proof&lt;/strong&gt;, a compact proof that basically says "the computation described by this contract was executed correctly using valid private inputs" without revealing what those inputs actually were, cool right?&lt;/p&gt;

&lt;p&gt;That proof doesn't just appear out of nowhere though. It requires the ZK circuits generated when your Compact contract is compiled, the verification keys that describe the circuit shape, and your actual witness data (balances, secrets, whatever the contract needs). The proof server is the process that takes all of that and produces the final zk-SNARK.&lt;/p&gt;

&lt;p&gt;The important design choice here: &lt;strong&gt;it runs locally&lt;/strong&gt;. Your private inputs never leave your machine. The server is a Docker container you run yourself, and the Midnight.js SDK talks to it over HTTP, i guess you understand all about proof servers now.&lt;/p&gt;

&lt;h3&gt;
  
  
  First-run behavior
&lt;/h3&gt;

&lt;p&gt;The first time you start the proof server, it has to fetch some artifacts. You'll see logs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO midnight_base_crypto::data_provider: Missing zero-knowledge verifying key
for Zswap inputs. Attempting to download from the host
https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the server pulling down the ZK verification keys and ZKIR (Zero-Knowledge Intermediate Representation) source files from Midnight's S3 bucket. Integrity is checked before anything is used. If a file's hash doesn't match, the server refuses to start.&lt;/p&gt;

&lt;p&gt;Once the download is done and caching is complete, you'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO actix_server::builder: starting 4 workers
INFO actix_server::server: starting service: "actix-web-service-0.0.0.0:6300",
workers: 4, listening on: 0.0.0.0:6300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's an Actix web server (Rust-based, very fast) spinning up with four worker threads on port &lt;code&gt;6300&lt;/code&gt;. This is the endpoint the SDK will hit when it needs a proof generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2pwydlrlzurjtkfvkk0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2pwydlrlzurjtkfvkk0.png" alt="Proof server startup logs" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Confirming it's working and active
&lt;/h3&gt;

&lt;p&gt;A quick server check for our local host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:6300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2026-04-21 10:13:12.154677419 +00:00:00"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you get that response, the server is ready to accept proof requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9cqm9nvp559m6jle5sp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9cqm9nvp559m6jle5sp.png" alt="Health check" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiring it to your DApp
&lt;/h3&gt;

&lt;p&gt;In your Midnight.js code, the SDK wrapper that talks to the proof server is &lt;code&gt;httpClientProofProvider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;httpClientProofProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/midnight-js-http-client-proof-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Points to your local proof server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proofProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;httpClientProofProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:6300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. From there, every time your DApp submits a transaction, the SDK bundles up the circuit + witness, sends it to &lt;code&gt;localhost:6300&lt;/code&gt;, waits for the proof, and attaches it to the unsigned transaction before submission.&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker setup for local development
&lt;/h2&gt;

&lt;p&gt;Both the proof server and the Indexer ship as Docker images. If you don't already have Docker on your machine, get that sorted first.&lt;/p&gt;

&lt;p&gt;For production, you can run your own Indexer and proof server on dedicated infrastructure, or use Midnight's hosted endpoints for Preview, Preprod, and Mainnet.&lt;/p&gt;

&lt;p&gt;On Ubuntu (I'm on 24.04):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker.io
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="c"&gt;# log out and back in, or:&lt;/span&gt;
newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify with &lt;code&gt;docker --version&lt;/code&gt; and &lt;code&gt;docker run hello-world&lt;/code&gt; before going further. If &lt;code&gt;docker run hello-world&lt;/code&gt; gives you a "permission denied" error, the group change hasn't taken effect yet. The &lt;code&gt;newgrp docker&lt;/code&gt; above usually fixes it without a full logout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the proof server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 6300:6300 midnightntwrk/proof-server:8.0.3 midnight-proof-server &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;-v&lt;/code&gt; flag on &lt;code&gt;midnight-proof-server&lt;/code&gt; enables verbose logging. Keep it on while you're learning. When something goes wrong, the extra output tells you exactly where.&lt;/li&gt;
&lt;li&gt;This command occupies your terminal. Open a new tab for everything else.&lt;/li&gt;
&lt;li&gt;First run pulls the image and downloads the ZK artifacts, so it takes several minutes. Subsequent runs are fast because Docker caches the image and the artifacts persist inside the container or probably in a volume if you mount one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running the Indexer
&lt;/h3&gt;

&lt;p&gt;For a fully local setup, run the standalone Indexer image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8088:8088 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;APP__INFRA__SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  midnightntwrk/indexer-standalone:4.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;APP__INFRA__SECRET&lt;/code&gt; is required. It is used to encrypt sensitive data the Indexer stores internally. Generating it with &lt;code&gt;openssl rand -hex 32&lt;/code&gt; gives you a clean 256-bit hex string.&lt;/p&gt;

&lt;p&gt;By default the standalone Indexer connects to a local Midnight node at &lt;code&gt;ws://localhost:9944&lt;/code&gt;, so if you want a fully self-contained stack you'll also need a node running. For most DApp development that's overkill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the hosted Indexer instead
&lt;/h3&gt;

&lt;p&gt;For development work, the simplest approach is to skip the standalone Indexer entirely and hit Midnight's hosted endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preview: &lt;code&gt;https://indexer.preview.midnight.network/api/v4/graphql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Preprod: &lt;code&gt;https://indexer.preprod.midnight.network/api/v4/graphql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Mainnet: &lt;code&gt;https://indexer.mainnet.midnight.network/api/v4/graphql&lt;/code&gt;
These are the same Indexer code, just running against Midnight's test and production networks. You get a fully-synced indexer for free, which is great when you're prototyping.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Errors you'll actually hit
&lt;/h3&gt;

&lt;p&gt;A few I've run into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;permission denied while trying to connect to the Docker daemon socket&lt;/code&gt;:&lt;/strong&gt; your user isn't in the &lt;code&gt;docker&lt;/code&gt; group yet. Run the &lt;code&gt;usermod&lt;/code&gt; and &lt;code&gt;newgrp&lt;/code&gt; commands above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bind: address already in use&lt;/code&gt; on port 6300:&lt;/strong&gt; something else is already bound to that port, or a previous container is still running. &lt;code&gt;docker ps&lt;/code&gt; to find it, &lt;code&gt;docker stop &amp;lt;container-id&amp;gt;&lt;/code&gt; to kill it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Cannot connect to the Docker daemon&lt;/code&gt;:&lt;/strong&gt; the daemon isn't running. &lt;code&gt;sudo systemctl start docker&lt;/code&gt;.
Those are some quick fixes to the issues i encountered while setting up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq38go0xlyj4z5gkqpx9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq38go0xlyj4z5gkqpx9.png" alt="Docker hello-world" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker tags and version pinning
&lt;/h2&gt;

&lt;p&gt;This is the single most important thing to get right, and also the easiest to get wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The proof server tag MUST match the Ledger version.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the current compatibility matrix at the time of writing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;proof server&lt;/td&gt;
&lt;td&gt;8.0.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ledger&lt;/td&gt;
&lt;td&gt;8.0.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexer&lt;/td&gt;
&lt;td&gt;4.0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;td&gt;0.22.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compact&lt;/td&gt;
&lt;td&gt;0.5.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why alignment matters
&lt;/h3&gt;

&lt;p&gt;The proof server generates proofs against a specific circuit format. The Ledger (the on-chain state machine) defines how those proofs are verified. Both sides have to agree on the exact format, verification keys, and field layout. If they don't, one of two things might happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The proof is rejected outright when your transaction hits the chain.&lt;/li&gt;
&lt;li&gt;Worse, the transaction silently fails in a way that's very hard to debug, because the proof itself looked structurally fine but encoded assumptions the ledger no longer holds.
You don't want to be debugging that at 2am late night XD. Just pin the versions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How to check
&lt;/h3&gt;

&lt;p&gt;Before pulling any image, check the official support matrix:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.midnight.network/relnotes/support-matrix" rel="noopener noreferrer"&gt;https://docs.midnight.network/relnotes/support-matrix&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then pin explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ledger is 8.0.3, so proof server must also be 8.0.3&lt;/span&gt;
docker pull midnightntwrk/proof-server:8.0.3   &lt;span class="c"&gt;# ✓ Correct&lt;/span&gt;
docker pull midnightntwrk/proof-server:7.0.0   &lt;span class="c"&gt;# ✗ Version mismatch&lt;/span&gt;
docker pull midnightntwrk/proof-server:latest  &lt;span class="c"&gt;# ✗ Never do this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never use &lt;code&gt;:latest&lt;/code&gt;.&lt;/strong&gt; Your setup might work today and break tomorrow for no obvious reason and you'll ship bugs that only appear on some machines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a note of your working combination in your repo's README.&lt;/strong&gt; When a teammate clones the project six months from now, that one line saves them an afternoon of confusion honestly XD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin every component together.&lt;/strong&gt; When you upgrade the Ledger also upgrade the proof server, the Node, and update your Compact compiler.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Querying the Indexer with GraphQL
&lt;/h2&gt;

&lt;p&gt;Now for the fun part. The Indexer's GraphQL API is where your DApp or a debugger reads on-chain data. Let's send some real queries to the Preview network endpoint and walk through what comes back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query 1: get the latest block
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://indexer.preview.midnight.network/api/v4/graphql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "{ block { hash height timestamp protocolVersion author } }"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"block"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2a4d888a..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;293425&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1776161520001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"protocolVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3a8a798e..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fun fact: my first query used &lt;code&gt;blocks&lt;/code&gt; instead of &lt;code&gt;block&lt;/code&gt; and the API corrected me. The error messages are actually helpful.&lt;/p&gt;

&lt;p&gt;Breaking down what you get back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hash&lt;/code&gt;:&lt;/strong&gt; the unique identifier for this block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;height&lt;/code&gt;:&lt;/strong&gt; block number, basically a counter that keeps going up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;timestamp&lt;/code&gt;:&lt;/strong&gt; Unix time in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;protocolVersion&lt;/code&gt;:&lt;/strong&gt; which version of the Midnight protocol this block was produced under. Useful for detecting upgrades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;author&lt;/code&gt;:&lt;/strong&gt; the validator (stake pool operator) who produced the block.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0sxlw3tyavz36p8avd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0sxlw3tyavz36p8avd3.png" alt="Latest block" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Query 2: fetch a specific block by height
&lt;/h3&gt;

&lt;p&gt;Pass an &lt;code&gt;offset&lt;/code&gt; argument to target a specific block. Here's the genesis block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://indexer.preview.midnight.network/api/v4/graphql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "{ block(offset: { height: 1 }) { hash height timestamp transactions { hash id protocolVersion contractActions { address } } } }"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also pulls in the transactions in that block, and for each transaction the contract actions it triggered. Running it against Preview returned the genesis block with its initial bootstrapping transaction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwdrpqgjihxbr693uyj5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwdrpqgjihxbr693uyj5g.png" alt="Genesis block" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Query 3: current epoch information
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://indexer.preview.midnight.network/api/v4/graphql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "{ currentEpochInfo { epochNo durationSeconds elapsedSeconds } }"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result showed &lt;code&gt;epochNo: 986757&lt;/code&gt;, &lt;code&gt;durationSeconds: 1800&lt;/code&gt; (a 30-minute epoch), and whatever &lt;code&gt;elapsedSeconds&lt;/code&gt; had accumulated by the time of the call. Handy when you're building anything that cares about staking cycles or time-based contract logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7h4ghisu43ju8vyyer3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7h4ghisu43ju8vyyer3.png" alt="Epoch info" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pro tip: schema introspection
&lt;/h3&gt;

&lt;p&gt;Don't memorize the schema. Ask for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://indexer.preview.midnight.network/api/v4/graphql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "{ __schema { queryType { fields { name description } } } }"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns every available top-level query along with its description. I do this every time I'm exploring a new version of the Indexer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ztmaihq1vk3gyqj6yri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ztmaihq1vk3gyqj6yri.png" alt=" Schema introspection:" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's available
&lt;/h3&gt;

&lt;p&gt;Top-level queries include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;block&lt;/code&gt;: get a block by hash or height (latest if no offset).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transactions&lt;/code&gt;: look up transactions by hash or identifier.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contractAction&lt;/code&gt;: fetch contract actions by contract address.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;currentEpochInfo&lt;/code&gt;: current epoch number and timing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spoCount&lt;/code&gt;: number of stake pool operators.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stakeDistribution&lt;/code&gt;: stake distribution across validators.&lt;/li&gt;
&lt;li&gt;Plus &lt;code&gt;dustGenerationStatus&lt;/code&gt;, &lt;code&gt;dParameterHistory&lt;/code&gt;, and others.
Useful block fields: &lt;code&gt;hash&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;protocolVersion&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;ledgerParameters&lt;/code&gt;, &lt;code&gt;parent&lt;/code&gt;, &lt;code&gt;transactions&lt;/code&gt;, &lt;code&gt;systemParameters&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful transaction fields: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;hash&lt;/code&gt;, &lt;code&gt;protocolVersion&lt;/code&gt;, &lt;code&gt;raw&lt;/code&gt;, &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;contractActions&lt;/code&gt;, &lt;code&gt;unshieldedCreatedOutputs&lt;/code&gt;, &lt;code&gt;unshieldedSpentOutputs&lt;/code&gt;, &lt;code&gt;zswapLedgerEvents&lt;/code&gt;, &lt;code&gt;dustLedgerEvents&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  WebSocket subscriptions for real-time updates
&lt;/h2&gt;

&lt;p&gt;Polling the Indexer for new data works but burns bandwidth and adds latency. For anything live (a wallet UI that updates when funds arrive, a dashboard that streams blocks, a DApp that reacts to contract state changes) you want subscriptions.&lt;/p&gt;

&lt;p&gt;The Indexer's GraphQL endpoint also accepts WebSocket connections, and the schema exposes a set of subscriptions you can tap into.&lt;/p&gt;

&lt;h3&gt;
  
  
  Available subscriptions
&lt;/h3&gt;

&lt;p&gt;Discovered via schema introspection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;blocks&lt;/code&gt;: subscribe to new blocks as they arrive, with an optional starting offset.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contractActions&lt;/code&gt;: stream contract actions filtered by contract address.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shieldedTransactions&lt;/code&gt;: shielded transaction events for a given session ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unshieldedTransactions&lt;/code&gt;: unshielded transaction events for a given address.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zswapLedgerEvents&lt;/code&gt;: ZSwap ledger events.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dustLedgerEvents&lt;/code&gt;: DUST ledger events.
### Why this matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference between polling and subscriptions looks small until you're running it at scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polling:&lt;/strong&gt; "anything new?" → no. "anything new?" → no. "anything new?" → yes, here. Every poll is a request, whether or not there's data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription:&lt;/strong&gt; you ask once, the Indexer pushes data to you whenever there's something to say.
For a block explorer or a live wallet view, this is the difference between a smooth UI and one that either lags or hammers the Indexer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Connecting with WebSocket
&lt;/h3&gt;

&lt;p&gt;Here's the shape of a minimal subscription client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://indexer.preview.midnight.network/api/v4/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Subscribe to new blocks&lt;/span&gt;
  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`subscription { blocks { hash height timestamp } }`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New block:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I haven't tested this WebSocket connection myself yet, so verify the protocol your Indexer version expects before using this in production.&lt;/p&gt;

&lt;p&gt;A note on the protocol: some GraphQL WebSocket servers use the legacy &lt;code&gt;subscriptions-transport-ws&lt;/code&gt; protocol (which is what the snippet above speaks), and some use the newer &lt;code&gt;graphql-ws&lt;/code&gt; protocol, which uses slightly different message types (&lt;code&gt;connection_init&lt;/code&gt;, &lt;code&gt;subscribe&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;). If the simple version doesn't work on your setup, check which protocol the endpoint expects and adjust the handshake accordingly.&lt;/p&gt;

&lt;p&gt;In practice, if you're using &lt;code&gt;indexerPublicDataProvider&lt;/code&gt; from the SDK (which we'll cover next), all of this is handled for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;indexerPublicDataProvider&lt;/code&gt; vs. direct GraphQL
&lt;/h2&gt;

&lt;p&gt;You now have two ways to read Indexer data: through the Midnight.js SDK, or by hitting the GraphQL endpoint directly. Both are valid; they're useful in different situations.&lt;/p&gt;

&lt;p&gt;To be honest, if you're just starting out, the direct GraphQL approach is easier to understand because you can see exactly what's happening.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SDK approach
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;indexerPublicDataProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/midnight-js-indexer-public-data-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicDataProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;indexerPublicDataProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://indexer.preview.midnight.network/api/v4/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://indexer.preview.midnight.network/api/v4/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this gets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A type-safe TypeScript interface: autocomplete, compile-time checks, the works.&lt;/li&gt;
&lt;li&gt;Clean integration with the rest of Midnight.js. &lt;code&gt;deployContract()&lt;/code&gt; and &lt;code&gt;findDeployedContract()&lt;/code&gt; both use this provider internally.&lt;/li&gt;
&lt;li&gt;Automatic serialization and deserialization: on-chain byte blobs become usable objects.&lt;/li&gt;
&lt;li&gt;Managed WebSocket subscription lifecycle: no manual reconnect logic.
### The direct approach
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://indexer.preview.midnight.network/api/v4/graphql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "{ block { hash height timestamp } }"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this gets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full control over the exact query shape.&lt;/li&gt;
&lt;li&gt;Zero dependency on TypeScript or Node.js. You can hit the endpoint from Python, Go, Rust, a shell script, or Postman.&lt;/li&gt;
&lt;li&gt;A fast debugging loop: no rebuild, no bundler, just a curl.&lt;/li&gt;
&lt;li&gt;Freedom to build tools that don't fit the SDK's assumptions (custom analytics, block explorers, monitoring).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Picking between them
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Building a DApp with Midnight.js&lt;/td&gt;
&lt;td&gt;&lt;code&gt;indexerPublicDataProvider&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging contract state&lt;/td&gt;
&lt;td&gt;Direct GraphQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building a block explorer&lt;/td&gt;
&lt;td&gt;Direct GraphQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom analytics or monitoring&lt;/td&gt;
&lt;td&gt;Direct GraphQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard contract deployment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;indexerPublicDataProvider&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How they relate
&lt;/h3&gt;

&lt;p&gt;The important thing to note: &lt;strong&gt;&lt;code&gt;indexerPublicDataProvider&lt;/code&gt; is a wrapper around the same GraphQL API.&lt;/strong&gt; Under the hood, the SDK is sending the same queries you'd send by hand. It just wraps them in a typed, cleaner interface that plays well with the rest of the Midnight.js ecosystem.&lt;/p&gt;

&lt;p&gt;So everything you learn from running raw GraphQL queries still helps you when you use the SDK later. Time spent poking at the GraphQL endpoint with curl makes you a better SDK user, because you develop intuition for what the SDK is actually doing. And if you ever need to step outside the SDK to build tooling, to debug a weird state, to automate something, you already know the shape of the API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The proof server and the Indexer are the two halves of how a Midnight DApp interacts with the network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;proof server:&lt;/strong&gt; privacy side. Generates ZK proofs locally so your private data never leaves your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexer:&lt;/strong&gt; data access side. Makes on-chain state queryable via GraphQL, with WebSocket subscriptions for real-time updates.
Before you go, remember: pin your Docker tags and check the support matrix religiously, use &lt;code&gt;indexerPublicDataProvider&lt;/code&gt; for building DApps and direct GraphQL for debugging, and use schema introspection whenever you want to explore what the Indexer can do.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Midnight docs: &lt;a href="https://docs.midnight.network/getting-started" rel="noopener noreferrer"&gt;https://docs.midnight.network/getting-started&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Support / compatibility matrix: &lt;a href="https://docs.midnight.network/relnotes/support-matrix" rel="noopener noreferrer"&gt;https://docs.midnight.network/relnotes/support-matrix&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Midnight Discord: &lt;a href="https://discord.com/invite/midnightnetwork" rel="noopener noreferrer"&gt;https://discord.com/invite/midnightnetwork&lt;/a&gt;
From here, check out the official tutorials for building your first contract on Midnight.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Share your feedback on X with #MidnightforDevs&lt;/p&gt;

</description>
      <category>midnightfordevs</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
