<?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.us-east-2.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>Why Your Ubuntu Laptop Lags, and How to Fix It for Free</title>
      <dc:creator>Usang Emmanuel </dc:creator>
      <pubDate>Tue, 23 Jun 2026 00:46:17 +0000</pubDate>
      <link>https://dev.to/emmanuellsensai/why-your-ubuntu-laptop-lags-and-how-to-fix-it-for-free-l0n</link>
      <guid>https://dev.to/emmanuellsensai/why-your-ubuntu-laptop-lags-and-how-to-fix-it-for-free-l0n</guid>
      <description>&lt;p&gt;My main work laptop is a Dell from 2017 with 8 GB of RAM. For weeks it had been crawling, freezing for whole seconds while I worked, and every so often it would simply switch itself off in the middle of a task. If you have ever lost unsaved work to a laptop that powers down on its own, you know exactly how frustrating that is.&lt;/p&gt;

&lt;p&gt;So I sat down and fixed it properly. The first thing I learned is worth saying up front: a slow, crashing laptop is usually &lt;strong&gt;two different problems wearing the same costume&lt;/strong&gt;. Treat them as one and you will chase your tail. Separate them, and both become fixable.&lt;/p&gt;

&lt;p&gt;Everything below is free and copy-paste ready. It was tested on Ubuntu 24.04 LTS, and it applies to almost any older Linux machine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The honest disclaimer:&lt;/strong&gt; Nobody can promise an old laptop will &lt;em&gt;never&lt;/em&gt; lag. Software cannot add cores or memory that are not physically there. But you can absolutely &lt;strong&gt;stop the freezes and shutdowns completely&lt;/strong&gt; and make everyday work feel smooth. That is the realistic, achievable goal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  0. Diagnose first, do not guess
&lt;/h2&gt;

&lt;p&gt;The biggest mistake is blindly applying "speed up Ubuntu" tweaks before knowing what is actually wrong. Spend five minutes measuring. Your lag has one of four common causes: &lt;strong&gt;heat, memory, disk, or a dying battery&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check temperature&lt;/strong&gt; (the usual cause of random shutdowns):&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;lm-sensors &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;sensors-detect &lt;span class="nt"&gt;--auto&lt;/span&gt;
sensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the &lt;code&gt;Core&lt;/code&gt; temperatures while you work. If they spike past &lt;strong&gt;90 to 100 °C&lt;/strong&gt; right before a crash, you have a thermal problem, not a software one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check memory&lt;/strong&gt; (the usual cause of freezing):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;htop &lt;span class="nt"&gt;-y&lt;/span&gt;
htop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;htop&lt;/code&gt;, watch the &lt;code&gt;Mem&lt;/code&gt; and &lt;code&gt;Swp&lt;/code&gt; bars during normal use. If memory pins near your limit and swap fills up, that thrashing &lt;em&gt;is&lt;/em&gt; your freeze.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check disk space&lt;/strong&gt; (a quiet killer):&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;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A root partition above &lt;strong&gt;90% full&lt;/strong&gt; makes Linux lag and turn unstable. Small SSDs fill up fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the crash logs and battery health:&lt;/strong&gt;&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;# What went wrong during the previous (crashed) session&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; err &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'thermal|temperature|critical'&lt;/span&gt;

&lt;span class="c"&gt;# Battery wear: compare 'energy-full' to 'energy-full-design'&lt;/span&gt;
upower &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;upower &lt;span class="nt"&gt;-e&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;BAT&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;energy-full&lt;/code&gt; has dropped well below the design figure, a worn battery plus a weak charger can cause instant power loss that looks just like a crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Fix the random shutdowns (heat)
&lt;/h2&gt;

&lt;p&gt;Start with the software layer:&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;# Intel's active thermal manager: throttles smartly instead of crashing&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;thermald &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; thermald
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The permanent fix:&lt;/strong&gt; On a laptop more than a few years old, the fan is clogged with dust and the CPU's thermal paste has dried out. Blow out the fan vents with compressed air and repaste the CPU. It is a 30 minute job, or a cheap one at a repair shop, and it cures heat-related shutdowns for good. No software substitutes for this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Fix the freezing (memory pressure)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Enable zram&lt;/strong&gt;, compressed swap that lives in RAM. This is the biggest single software win on a low-memory machine:&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;zram-tools &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"ALGO=zstd&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;PERCENT=50"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/default/zramswap
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart zramswap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tune swappiness&lt;/strong&gt; so the kernel prefers fast zram:&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;echo&lt;/span&gt; &lt;span class="s1"&gt;'vm.swappiness=100'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/sysctl.d/99-swappiness.conf
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;--system&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install earlyoom&lt;/strong&gt;, the anti-freeze safety net. When RAM is fully used up, Linux can hang for &lt;em&gt;minutes&lt;/em&gt;. earlyoom steps in early, kills the single heaviest process, and keeps your desktop responsive:&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;earlyoom &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; earlyoom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Reclaim disk space
&lt;/h2&gt;

&lt;p&gt;Safe cleanup first:&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 autoremove &lt;span class="nt"&gt;--purge&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;        &lt;span class="c"&gt;# old packages + unused kernels&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt clean                         &lt;span class="c"&gt;# cached .deb files&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;--vacuum-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200M     &lt;span class="c"&gt;# trim system logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use Docker, it is almost always the biggest disk hog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker system &lt;span class="nb"&gt;df&lt;/span&gt;                       &lt;span class="c"&gt;# see what Docker is using&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Careful, this deletes data:&lt;/strong&gt; &lt;code&gt;docker system prune -a --volumes&lt;/code&gt; removes unused images, containers &lt;strong&gt;and volumes&lt;/strong&gt;. Volumes can hold databases. Run it deliberately. Drop &lt;code&gt;--volumes&lt;/code&gt; if you only want to clear images and stopped containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Lighten the desktop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Turn off window animations&lt;/span&gt;
gsettings &lt;span class="nb"&gt;set &lt;/span&gt;org.gnome.desktop.interface enable-animations &lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# See if the file indexer is eating CPU in the background&lt;/span&gt;
tracker3 status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then trim startup apps (search &lt;em&gt;"Startup Applications"&lt;/em&gt;), keep browser tabs under control, and disable GNOME extensions you do not use. Your &lt;strong&gt;browser is usually a bigger memory hog than everything else combined&lt;/strong&gt;, so turn on its built-in memory saver or tab discarding.&lt;/p&gt;

&lt;p&gt;For a real step-change on very old hardware, install a lighter session and pick it at the login screen:&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;xubuntu-desktop &lt;span class="nt"&gt;-y&lt;/span&gt;    &lt;span class="c"&gt;# XFCE: light and fast (optional)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Check the SSD and update firmware
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find your disk's device name first&lt;/span&gt;
lsblk

&lt;span class="c"&gt;# Health check (use nvme0n1 OR sda, whichever lsblk shows)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;smartmontools &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;smartctl &lt;span class="nt"&gt;-H&lt;/span&gt; /dev/nvme0n1

&lt;span class="c"&gt;# Update BIOS / firmware (many laptops are supported)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;fwupdmgr refresh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;fwupdmgr update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. What NOT to waste time on
&lt;/h2&gt;

&lt;p&gt;Popular advice that will not move the needle on a modern Ubuntu release. Skip these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;preload:&lt;/strong&gt; largely obsolete with SSDs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"RAM cleaner" apps and scripts:&lt;/strong&gt; placebo, Linux manages memory well already&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reinstalling Ubuntu:&lt;/strong&gt; rarely the real fix, and it costs you a day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggressive CPU-governor hacks:&lt;/strong&gt; modern defaults are already sensible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. The real ceiling (the honest part)
&lt;/h2&gt;

&lt;p&gt;Everything above will stop the crashes and smooth out daily use. But two hardware truths set the actual ceiling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean and repaste the cooling:&lt;/strong&gt; the permanent cure for heat-driven shutdowns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add RAM:&lt;/strong&gt; if you run containers, dev servers, or many browser tabs, more memory does more than every software tweak combined. Check your model's maximum capacity and slot count before buying.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do the software fixes today, do the two hardware steps when you can, and you have solved the problem at the root instead of papering over it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 60-second version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Most likely cause&lt;/th&gt;
&lt;th&gt;Do this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Random shutdown&lt;/td&gt;
&lt;td&gt;Overheating or weak battery&lt;/td&gt;
&lt;td&gt;thermald, then clean + repaste&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long freezes&lt;/td&gt;
&lt;td&gt;Out of RAM&lt;/td&gt;
&lt;td&gt;zram + earlyoom + swappiness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General lag&lt;/td&gt;
&lt;td&gt;Full disk or heavy desktop&lt;/td&gt;
&lt;td&gt;cleanup + disable animations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still slow&lt;/td&gt;
&lt;td&gt;Not enough RAM&lt;/td&gt;
&lt;td&gt;Upgrade memory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;After all of this, my laptop went from freezing every few minutes to running smoothly through a full work day. It still slows down when I push it really hard, because 8 GB is 8 GB, but the freezes and the random shutdowns are gone.&lt;/p&gt;

&lt;p&gt;If this helped you rescue an old machine, drop a comment with what worked for you, and share it with someone still fighting a laggy laptop. Follow me for more practical Linux and developer workflow tips.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>LUMINA 3D LOGO GENERATOR WEB APP</title>
      <dc:creator>Usang Emmanuel </dc:creator>
      <pubDate>Thu, 23 Apr 2026 14:56:47 +0000</pubDate>
      <link>https://dev.to/emmanuellsensai/lumina-3d-logo-generator-web-app-50bc</link>
      <guid>https://dev.to/emmanuellsensai/lumina-3d-logo-generator-web-app-50bc</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is my submission for &lt;a href="https://dev.to/deved/build-apps-with-google-ai-studio"&gt;DEV Education Track: Build Apps with Google AI Studio&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I Built a 3D logo generator app using Google studio which builds based on the user's decription for their logo in realtime&lt;/p&gt;

&lt;p&gt;the prompt: i want you to build a web app that generates 3D LOGOs for business ideas and established businesses, ultra realistic logos with different templates for users to select from as well as decription panels for them to type the logo description, incorporate image generation with the Imagen API and using google API too for accurate logo details, a very user friendly and professional UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2Fr7gwjo3n703xs2b54b86.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%2Fr7gwjo3n703xs2b54b86.png" alt="LUMINA WEB APP" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience
&lt;/h2&gt;

&lt;p&gt;I Learnt how to use google studio in less than 2hrs and i must say, i'm impressed by the high attention rate of this app to details.&lt;/p&gt;

</description>
      <category>deved</category>
      <category>learngoogleaistudio</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <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 will 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 will have a working local stack and a mental model for how a Midnight transaction actually moves from your wallet to the chain and back.&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 are used to on Ethereum or Solana. There is 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.&lt;/p&gt;

&lt;p&gt;That proof does not 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.&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 will 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 is 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 does not match, the server refuses to start.&lt;/p&gt;

&lt;p&gt;Once the download is done and caching is complete, you will 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 is 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 is 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;From there, every time your DApp submits a transaction, the SDK bundles up the circuit and 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 do not 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 am 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 has not 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 are 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.&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. The correct tag depends on your target network:&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;# For Preview network&lt;/span&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.3.2

&lt;span class="c"&gt;# For Preprod / Mainnet&lt;/span&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 will also need a node running. For most DApp development that is 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;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&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 are prototyping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Errors you will actually hit
&lt;/h3&gt;

&lt;p&gt;A few I have 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 is not 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 is not running. &lt;code&gt;sudo systemctl start docker&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are some quick fixes to the issues I encountered while setting up.&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%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 version must be compatible with the ledger version your target network runs.&lt;/strong&gt; Always check the support matrix for the exact pairing before pulling images.&lt;/p&gt;

&lt;p&gt;Here is 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;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node (Midnight)&lt;/td&gt;
&lt;td&gt;1.0.0&lt;/td&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node (Midnight)&lt;/td&gt;
&lt;td&gt;0.22.5&lt;/td&gt;
&lt;td&gt;Preprod / Mainnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compact devtools (&lt;code&gt;compact&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;0.5.1&lt;/td&gt;
&lt;td&gt;Installs compilers, manages versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compact compiler (&lt;code&gt;compact compile&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;0.31.0&lt;/td&gt;
&lt;td&gt;Contract compiler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compact runtime&lt;/td&gt;
&lt;td&gt;0.16.0&lt;/td&gt;
&lt;td&gt;Runtime library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-chain runtime&lt;/td&gt;
&lt;td&gt;3.0.0&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ledger&lt;/td&gt;
&lt;td&gt;8.1.0&lt;/td&gt;
&lt;td&gt;Preview&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;td&gt;Preprod / Mainnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Midnight.js&lt;/td&gt;
&lt;td&gt;4.1.1&lt;/td&gt;
&lt;td&gt;DApp framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Midnight Indexer&lt;/td&gt;
&lt;td&gt;4.3.2&lt;/td&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Midnight Indexer&lt;/td&gt;
&lt;td&gt;4.0.1&lt;/td&gt;
&lt;td&gt;Preprod / Mainnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof server&lt;/td&gt;
&lt;td&gt;8.0.3&lt;/td&gt;
&lt;td&gt;ZKP generation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Version matrix verified against the live support matrix on June 5, 2026. Always check &lt;a href="https://docs.midnight.network/relnotes/support-matrix" rel="noopener noreferrer"&gt;https://docs.midnight.network/relnotes/support-matrix&lt;/a&gt; before pulling images, as versions change between network upgrades.&lt;/em&gt;&lt;/p&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 do not, 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 is very hard to debug, because the proof itself looked structurally fine but encoded assumptions the ledger no longer holds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You do not want to be debugging that at 2 AM. Just pin the versions.&lt;/p&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;# Proof server 8.0.3 is compatible with Ledger 8.0.3 (Preprod/Mainnet)&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 will 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.&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 is 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 are 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;
  
  
  Query 4: read contract state by address
&lt;/h3&gt;

&lt;p&gt;This is the query a real DApp actually needs. Once you have a deployed contract address, you can fetch its current on-chain state:&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 http://localhost:8088/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": "{ contractAction(address: \"99d36f1c860ee0cf83b23b1b90ca61e9bd2b32597ae2c09dd3fae3ecc7ed0bd5\") { address state transaction { hash } } }"}'&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;"contractAction"&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;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"99d36f1c860ee0cf83b23b1b90ca61e9bd2b32597ae2c09dd3fae3ecc7ed0bd5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6d69646e696768743a636f6e74726163742d73746174655b76365d..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"transaction"&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;"b5f984348ba73e0f747df7f1da886159d48d3a6cc5514abf18656f9030cc8ee7"&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;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;Breaking down what comes back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;address&lt;/code&gt;:&lt;/strong&gt; the contract's unique identifier on the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;state&lt;/code&gt;:&lt;/strong&gt; the hex-encoded on-chain state of the contract. This is the serialized WASM state blob. Your DApp deserializes it using the contract's TypeScript bindings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;transaction&lt;/code&gt;:&lt;/strong&gt; the transaction that produced this state update, identified by its hash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;ContractAction&lt;/code&gt; type also exposes &lt;code&gt;zswapState&lt;/code&gt;, &lt;code&gt;unshieldedBalances&lt;/code&gt;, and a reference back to the full &lt;code&gt;transaction&lt;/code&gt; object if you need deeper context.&lt;/p&gt;

&lt;p&gt;I deployed a Hello World contract using create-mn-app to get a real contract address to demonstrate this query:&lt;br&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%2Fojwzkgiuec7csf9lv6l0.jpg" 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%2Fojwzkgiuec7csf9lv6l0.jpg" alt="Contract deployed successfully" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Pro tip: schema introspection
&lt;/h3&gt;

&lt;p&gt;Do not 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 am 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 is 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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;/p&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.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The difference between polling and subscriptions looks small until you are 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 is 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 is something to say.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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;/p&gt;

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

&lt;p&gt;The correct endpoint for WebSocket subscriptions adds a &lt;code&gt;/ws&lt;/code&gt; suffix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wss://indexer.preview.midnight.network/api/v4/graphql/ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The protocol is &lt;code&gt;graphql-transport-ws&lt;/code&gt;, not the legacy &lt;code&gt;start&lt;/code&gt; message type. The correct handshake is &lt;code&gt;connection_init&lt;/code&gt; then &lt;code&gt;connection_ack&lt;/code&gt; then &lt;code&gt;subscribe&lt;/code&gt; then &lt;code&gt;next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is a working Node.js subscription client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&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/ws&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;graphql-transport-ws&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="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;Connection opened, sending connection_init...&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;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;connection_init&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="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;msg&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;Received:&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection_ack&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;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;Acknowledged. Subscribing to blocks...&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;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;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;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;subscribe&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subscription { blocks { hash height timestamp } }&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&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;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;&amp;gt;&amp;gt;&amp;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;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="nx"&gt;msg&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;error&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this against the Preview network produces a live stream of blocks as they are produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Connection opened, sending connection_init...
Received: {"type":"connection_ack"}
Acknowledged. Subscribing to blocks...
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; NEW BLOCK: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="go"&gt;  "hash": "0a1332ee9e9df5c0d189fecf0abe5295622a87b14b79fdf79e90dab3b13f0725",
  "height": 1044844,
  "timestamp": 1780671888000
}
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; NEW BLOCK: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="go"&gt;  "hash": "15fcfcab66b95f7c38acbe6f16e62479347cc20161f10c49c40487da972f0cee",
  "height": 1044845,
  "timestamp": 1780671894001
}
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; NEW BLOCK: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="go"&gt;  "hash": "1ea601c792222aceaa66ad305592cef3ddeef98d66de0fcab76e9936f900f9ca",
  "height": 1044846,
  "timestamp": 1780671900000
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;next&lt;/code&gt; message carries the new block data as it arrives. No polling required.&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%2Fxsuy0g6ds8035y260fw3.jpg" 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%2Fxsuy0g6ds8035y260fw3.jpg" alt="WebSocket streaming live blocks" width="800" height="450"&gt;&lt;/a&gt;&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 and useful in different situations.&lt;/p&gt;

&lt;p&gt;To be honest, if you are just starting out, the direct GraphQL approach is easier to understand because you can see exactly what is 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;provider&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/ws&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;The main thing this provider exists to do is &lt;code&gt;contractStateObservable&lt;/code&gt;, which subscribes to a deployed contract's live state:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONTRACT_ADDRESS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;99d36f1c860ee0cf83b23b1b90ca61e9bd2b32597ae2c09dd3fae3ecc7ed0bd5&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;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contractStateObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;CONTRACT_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&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;latest&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="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&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="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;Contract state received:&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;Running this against a locally deployed Hello World contract returns the live contract state object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subscribing to contract state via indexerPublicDataProvider...
Contract state received:
{
  "__wbg_ptr": 1188192
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;__wbg_ptr&lt;/code&gt; value is a WebAssembly memory pointer. The SDK has deserialized the raw hex state blob from the Indexer into a live WASM object your contract's TypeScript bindings can work with directly. This is the abstraction &lt;code&gt;indexerPublicDataProvider&lt;/code&gt; provides over raw GraphQL.&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%2Fu0c0gursq6zje1tcrdy3.jpg" 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%2Fu0c0gursq6zje1tcrdy3.jpg" alt="indexerPublicDataProvider" width="797" height="105"&gt;&lt;/a&gt;&lt;/p&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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The direct approach
&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 } }"}'&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 do not 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 would 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 exploring 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, or 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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;/p&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;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From here, check out the official tutorials for building your first contract on Midnight.&lt;/p&gt;

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