<?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: Arihant Agarwal</title>
    <description>The latest articles on DEV Community by Arihant Agarwal (@arihant_agarwal_359eb9f57).</description>
    <link>https://dev.to/arihant_agarwal_359eb9f57</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%2F3854331%2F26fd4dd5-236a-4a91-b299-48e66ae0aae6.jpg</url>
      <title>DEV Community: Arihant Agarwal</title>
      <link>https://dev.to/arihant_agarwal_359eb9f57</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arihant_agarwal_359eb9f57"/>
    <language>en</language>
    <item>
      <title>Security Checklist for Midnight dApps Before Deployment</title>
      <dc:creator>Arihant Agarwal</dc:creator>
      <pubDate>Sun, 10 May 2026 23:30:59 +0000</pubDate>
      <link>https://dev.to/arihant_agarwal_359eb9f57/security-checklist-for-midnight-dapps-before-deployment-69m</link>
      <guid>https://dev.to/arihant_agarwal_359eb9f57/security-checklist-for-midnight-dapps-before-deployment-69m</guid>
      <description>&lt;p&gt;Midnight gives DApp developers a different security model from public-by-default chains. A Midnight smart contract can keep sensitive data local, prove facts about that data with ZK proofs, and write only selected outputs to public ledger state. That power creates a clear deployment responsibility: every public write, every witness value, every authorization check, and every proof-generation path must be reviewed before launch.&lt;/p&gt;

&lt;p&gt;Use this tutorial as a durable pre-deployment checklist. It is written for developers who already have a Compact smart contract and want a repeatable audit process before deploying to Preview, Preprod, or Mainnet.&lt;/p&gt;

&lt;p&gt;This guide is for Midnight DApp developers who write Compact smart contracts, implement TypeScript witnesses, and configure Midnight.js or related tooling for proof generation and transaction submission.&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;Node.js 22 or later.&lt;/li&gt;
&lt;li&gt;Docker Desktop or another Docker-compatible runtime.&lt;/li&gt;
&lt;li&gt;The Compact devtools installed.&lt;/li&gt;
&lt;li&gt;A compiled Midnight smart contract in a project with TypeScript tests.&lt;/li&gt;
&lt;li&gt;Access to a local proof server or the correct Preview, Preprod, or Mainnet proof server endpoint.&lt;/li&gt;
&lt;li&gt;Lace configured for the target environment when wallet interaction is part of your test flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Required reading
&lt;/h2&gt;

&lt;p&gt;Review these pages before running the checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight developer documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/compact" rel="noopener noreferrer"&gt;Compact language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/compact/smart-contract-security" rel="noopener noreferrer"&gt;Smart contract security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/compact/reference/explicit-disclosure" rel="noopener noreferrer"&gt;Explicit disclosure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/compact/test-and-debug" rel="noopener noreferrer"&gt;Test and debug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/relnotes/support-matrix" rel="noopener noreferrer"&gt;Compatibility matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/relnotes/network" rel="noopener noreferrer"&gt;Environments and endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/ai-tools/midnight-mcp-ai-assisted-development" rel="noopener noreferrer"&gt;Midnight MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/examples/dapps/bboard" rel="noopener noreferrer"&gt;Bulletin board DApp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start with a clean verification gate
&lt;/h2&gt;

&lt;p&gt;Run the same gate before every security review and keep it in CI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact &lt;span class="nt"&gt;--version&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--version&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--language-version&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--runtime-version&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--ledger-version&lt;/span&gt;
compact format &lt;span class="nt"&gt;--check&lt;/span&gt; src/
compact fixup &lt;span class="nt"&gt;--check&lt;/span&gt; src/
npm run compact
npm run typecheck
npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the official bulletin board example, the Compact script compiles the source smart contract into generated TypeScript, JavaScript, ZKIR, proving keys, verifier keys, and compiler metadata:&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;"scripts"&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;"compact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"compact compile src/bboard.compact ./src/managed/bboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ci"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run compact &amp;amp;&amp;amp; npm run typecheck &amp;amp;&amp;amp; npm run lint &amp;amp;&amp;amp; npm run build &amp;amp;&amp;amp; npm run test"&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;Do not review stale generated files. Delete generated output, recompile, run tests, and review generated metadata from the deployment commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the security boundary
&lt;/h2&gt;

&lt;p&gt;A Compact smart contract spans three contexts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Public ledger state:&lt;/strong&gt; On-chain state that observers, indexers, and DApps can read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ZK circuits:&lt;/strong&gt; Compact logic that proves valid execution without revealing private inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local witnesses:&lt;/strong&gt; TypeScript or JavaScript callbacks that run on the user's machine and supply private values.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most Midnight bugs appear at the boundary between those contexts. The compiler helps prevent accidental disclosure, but it cannot decide whether a disclosure is safe for your logic or make a witness implementation trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Audit every &lt;code&gt;disclose()&lt;/code&gt; call
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;disclose()&lt;/code&gt; wrapper is an explicit statement that a value derived from witness data, exported circuit arguments, or constructor arguments is safe to place in a public context. Public contexts include public ledger assignments, values returned from exported circuits, and values passed to another smart contract.&lt;/p&gt;

&lt;p&gt;Start with a simple inventory:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"disclose("&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; src contract contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each result, write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exact value being disclosed.&lt;/li&gt;
&lt;li&gt;The private inputs or witness values that can influence it.&lt;/li&gt;
&lt;li&gt;The public destination, such as &lt;code&gt;export ledger owner&lt;/code&gt;, an exported circuit return value, or a cross-smart-contract argument.&lt;/li&gt;
&lt;li&gt;The reason that disclosure is necessary.&lt;/li&gt;
&lt;li&gt;The privacy impact if a user links this value across transactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A safe &lt;code&gt;disclose()&lt;/code&gt; sits close to the public write. Avoid disclosing a private value early and passing it through multiple branches. That makes later review harder.&lt;/p&gt;

&lt;p&gt;The bulletin board ownership pattern shows a narrow disclosure. The smart contract does not disclose the local secret key. It discloses a hash commitment derived from a domain separator, the current sequence number, and the local secret key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.22;

import CompactStandardLibrary;

export enum State {
  VACANT,
  OCCUPIED
}

export ledger state: State;
export ledger message: Maybe&amp;lt;Opaque&amp;lt;"string"&amp;gt;&amp;gt;;
export ledger sequence: Counter;
export ledger owner: Bytes&amp;lt;32&amp;gt;;

constructor() {
  state = State.VACANT;
  message = none&amp;lt;Opaque&amp;lt;"string"&amp;gt;&amp;gt;();
  sequence.increment(1);
}

witness localSecretKey(): Bytes&amp;lt;32&amp;gt;;

export circuit post(newMessage: Opaque&amp;lt;"string"&amp;gt;): [] {
  assert(state == State.VACANT, "Attempted to post to an occupied board");
  owner = disclose(publicKey(localSecretKey(), sequence as Field as Bytes&amp;lt;32&amp;gt;));
  message = disclose(some&amp;lt;Opaque&amp;lt;"string"&amp;gt;&amp;gt;(newMessage));
  state = State.OCCUPIED;
}

export circuit takeDown(): Opaque&amp;lt;"string"&amp;gt; {
  assert(state == State.OCCUPIED, "Attempted to take down post from an empty board");
  assert(owner == publicKey(localSecretKey(), sequence as Field as Bytes&amp;lt;32&amp;gt;), "Attempted to take down post, but not the current owner");
  const formerMsg = message.value;
  state = State.VACANT;
  sequence.increment(1);
  message = none&amp;lt;Opaque&amp;lt;"string"&amp;gt;&amp;gt;();
  return formerMsg;
}

export circuit publicKey(sk: Bytes&amp;lt;32&amp;gt;, sequence: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  return persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([pad(32, "bboard:pk:"), sequence, sk]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; differently. &lt;code&gt;owner&lt;/code&gt; is safe only because it is a one-way, domain-separated commitment. &lt;code&gt;message&lt;/code&gt; is intentionally public. The compiler enforces explicitness. You enforce policy.&lt;/p&gt;

&lt;p&gt;Pass this section only when every disclosure has a documented purpose and a negative privacy test.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Review &lt;code&gt;ownPublicKey()&lt;/code&gt; usage
&lt;/h2&gt;

&lt;p&gt;Search for &lt;code&gt;ownPublicKey()&lt;/code&gt; before any deployment:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"ownPublicKey"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; src contract contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A match in authorization code is a release blocker. Midnight's security guidance treats &lt;code&gt;ownPublicKey()&lt;/code&gt; as a witness function. A DApp frontend can provide witness results, so a circuit must not use &lt;code&gt;ownPublicKey()&lt;/code&gt; as proof that the caller owns the matching secret key.&lt;/p&gt;

&lt;p&gt;Prefer a witness-based commitment pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep the secret key in local private state.&lt;/li&gt;
&lt;li&gt;Return it through a witness such as &lt;code&gt;localSecretKey()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Derive a DApp-specific commitment with &lt;code&gt;persistentHash&lt;/code&gt; and a clear domain separator.&lt;/li&gt;
&lt;li&gt;Store only the commitment in public ledger state.&lt;/li&gt;
&lt;li&gt;Recompute the commitment inside protected circuits and assert equality against public state.&lt;/li&gt;
&lt;li&gt;Include a sequence, round, or nonce when linkability matters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the bulletin board pattern, &lt;code&gt;post()&lt;/code&gt; stores &lt;code&gt;owner&lt;/code&gt; as a commitment. Later, &lt;code&gt;takeDown()&lt;/code&gt; recomputes the same commitment with &lt;code&gt;localSecretKey()&lt;/code&gt; and the current &lt;code&gt;sequence&lt;/code&gt;. The caller proves knowledge of the private input that opens the commitment without revealing the secret key.&lt;/p&gt;

&lt;p&gt;Your review should classify each authorization path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Allowed:&lt;/strong&gt; &lt;code&gt;assert(publicKey(localSecretKey(), round) == owner, "Operation not permitted")&lt;/code&gt; when &lt;code&gt;owner&lt;/code&gt; is a stored commitment for that round.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allowed after another check:&lt;/strong&gt; &lt;code&gt;ownPublicKey()&lt;/code&gt; used for display, routing, or wallet-related logic after the caller has passed an independent proof.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocked:&lt;/strong&gt; &lt;code&gt;assert(ownPublicKey() == owner, "Only owner")&lt;/code&gt; or any equivalent caller-verification check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pass this section only when no exported circuit depends on &lt;code&gt;ownPublicKey()&lt;/code&gt; for caller verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Verify replay protection with nonces or nullifiers
&lt;/h2&gt;

&lt;p&gt;A valid proof can still be dangerous if it can be reused in the wrong context. Replay protection binds an action to a unique state, round, nonce, or resource.&lt;/p&gt;

&lt;p&gt;Search for replay primitives and state-versioning fields:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"Counter&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;nonce&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;nullifier&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;claimZswapNullifier&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;sequence&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;round"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; src contract contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a &lt;code&gt;Counter&lt;/code&gt;, &lt;code&gt;round&lt;/code&gt;, or explicit nonce for state-machine actions. The bulletin board example uses &lt;code&gt;sequence&lt;/code&gt; for this. The current poster's commitment includes the current &lt;code&gt;sequence&lt;/code&gt;. When the message is removed, &lt;code&gt;sequence.increment(1)&lt;/code&gt; invalidates the old commitment for the next post. This prevents old ownership proofs from applying to a later board cycle.&lt;/p&gt;

&lt;p&gt;Use nullifiers for one-time resources, shielded notes, private claims, coupons, votes, withdrawals, or any operation where a private resource must be consumed exactly once. The ledger kernel exposes &lt;code&gt;claimZswapNullifier(nul: Bytes&amp;lt;32&amp;gt;)&lt;/code&gt; to require a nullifier in the containing transaction and prevent another call from claiming the same nullifier in that transaction.&lt;/p&gt;

&lt;p&gt;Your replay review should answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What makes this call unique?&lt;/li&gt;
&lt;li&gt;Does the uniqueness value appear inside the commitment or nullifier derivation?&lt;/li&gt;
&lt;li&gt;Does the smart contract update the uniqueness value after a successful call?&lt;/li&gt;
&lt;li&gt;Can a stale proof apply after state changes?&lt;/li&gt;
&lt;li&gt;Can two calls in the same transaction consume the same resource?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add negative tests for replay attempts. A good test calls the same action twice with the same private input and the same public state snapshot, then expects the second call to fail or produce a different required state boundary. For nullifier flows, add a test that attempts to consume the same private resource twice and expects rejection.&lt;/p&gt;

&lt;p&gt;Pass this section only when every state-changing circuit has an explicit anti-replay story and a negative test.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Review exported ledger fields
&lt;/h2&gt;

&lt;p&gt;Anything declared with &lt;code&gt;export ledger&lt;/code&gt; is part of the public interface. Treat exported ledger fields as API surface. They are useful for DApps, indexers, and tests, but they also reveal information.&lt;/p&gt;

&lt;p&gt;Search all ledger declarations:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"export[[:space:]]&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="s2"&gt;ledger&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;sealed[[:space:]]&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="s2"&gt;ledger&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;^[[:space:]]*ledger"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; src contract contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each ledger field, classify the field as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public by design:&lt;/strong&gt; state enums, counters, public messages, public commitments, public configuration, and public totals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public but sensitive:&lt;/strong&gt; hashes, commitments, status flags, timestamps, or counters that could link users or reveal behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not acceptable:&lt;/strong&gt; raw secrets, raw private identifiers, raw credentials, hidden vote choices, private notes, unblinded amounts, or values that reconstruct private state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;sealed ledger&lt;/code&gt; for values that must be initialized once and stay immutable, such as domain separators, policy IDs, configured limits, or deployment parameters. A sealed field can be set during initialization, but exported circuits cannot modify it later.&lt;/p&gt;

&lt;p&gt;Compact toolchain 0.31.0 adds public ledger state layout to &lt;code&gt;contract-info.json&lt;/code&gt;. After compilation, inspect that generated file and compare it with your manual ledger inventory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact compile src/bboard.compact src/managed/bboard
&lt;span class="nb"&gt;cat &lt;/span&gt;src/managed/bboard/compiler/contract-info.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not rely on source review alone. Generated metadata catches fields that appear through modules, imports, and generated layouts. Keep a ledger review file with this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Field | Exported | Sealed | Type | Public reason | Privacy risk | Approved by |
| --- | --- | --- | --- | --- | --- | --- |
| state | yes | no | State | DApp renders board status | Low | Security reviewer |
| message | yes | no | Maybe&lt;span class="nt"&gt;&amp;lt;Opaque&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;"&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&amp;gt; | Message is intentionally public | User content is public | Product owner |
| sequence | yes | no | Counter | Replay boundary | May reveal activity count | Security reviewer |
| owner | yes | no | Bytes&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;32&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; | Ownership commitment | Linkable within one sequence | Security reviewer |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass this section only when every exported field has a documented public reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Verify witness implementation correctness
&lt;/h2&gt;

&lt;p&gt;Witness declarations in Compact have no body. The DApp implements them in TypeScript or JavaScript. That means witness logic belongs in the security review.&lt;/p&gt;

&lt;p&gt;A witness implementation returns a tuple: the new private state and the value passed back into the circuit. The bulletin board witness returns the same private state and the user's local secret key:&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;Ledger&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="s2"&gt;./managed/bboard/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WitnessContext&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="s2"&gt;@midnight-ntwrk/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BBoardPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createBBoardPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&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;secretKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;witnesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;localSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;privateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;WitnessContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Ledger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BBoardPrivateState&lt;/span&gt;&lt;span class="o"&gt;&amp;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;BBoardPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Uint8Array&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;privateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretKey&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;Review every witness for these requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The return type matches the Compact declaration exactly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt; maps to a &lt;code&gt;Uint8Array&lt;/code&gt; with length 32.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Uint&lt;/code&gt; values use &lt;code&gt;bigint&lt;/code&gt; where generated types require it.&lt;/li&gt;
&lt;li&gt;The witness does not read from remote services during proof generation unless that dependency is authenticated, stable, and tested.&lt;/li&gt;
&lt;li&gt;The witness does not log secrets, notes, nullifiers, credentials, raw votes, or private amounts.&lt;/li&gt;
&lt;li&gt;The witness updates private state deterministically when a later circuit call depends on that update.&lt;/li&gt;
&lt;li&gt;The circuit validates the returned value before relying on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat witness values as untrusted inputs. If a witness returns a balance, membership flag, score, credential, or secret, the circuit must verify it through commitments, Merkle paths, signatures, nullifiers, public state, or another cryptographic check. Do not accept a witness result because your own frontend returns the expected value.&lt;/p&gt;

&lt;p&gt;Add tests for malicious witness values. Replace a valid secret key with another 32-byte value and expect authorization to fail. Return an out-of-range amount and expect an assertion. Return a stale Merkle path and expect rejection.&lt;/p&gt;

&lt;p&gt;Pass this section only when every witness has type tests, negative tests, and a circuit-level validation path.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Confirm version compatibility
&lt;/h2&gt;

&lt;p&gt;Version mismatches create confusing failures. A proof can fail when generated files, runtime packages, ledger version, and proof server version do not match.&lt;/p&gt;

&lt;p&gt;Use the compatibility matrix as the source of truth for the release you target. At the time of this tutorial, the latest tested stack lists Compact devtools &lt;code&gt;0.5.1&lt;/code&gt;, Compact toolchain &lt;code&gt;0.31.0&lt;/code&gt;, Compact runtime &lt;code&gt;0.16.0&lt;/code&gt;, Ledger &lt;code&gt;8.0.3&lt;/code&gt;, Midnight.js &lt;code&gt;4.0.4&lt;/code&gt;, testkit-js &lt;code&gt;4.0.4&lt;/code&gt;, and proof server &lt;code&gt;8.0.3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run these checks in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact list &lt;span class="nt"&gt;--installed&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--version&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--language-version&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--runtime-version&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--ledger-version&lt;/span&gt;
npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'@midnight-ntwrk'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin versions in &lt;code&gt;package.json&lt;/code&gt;. Do not mix Ledger 8 generated files with a Ledger 7 proof server or older Midnight.js packages. Recompile after every dependency update, even when TypeScript still passes.&lt;/p&gt;

&lt;p&gt;Upgrade flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact update 0.31
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node_modules package-lock.json
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run compact
npm run typecheck
npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After upgrading, inspect generated files and &lt;code&gt;contract-info.json&lt;/code&gt; again. Toolchain 0.31.0 includes public ledger layout in that file.&lt;/p&gt;

&lt;p&gt;Pass this section only when versions match the target environment and generated files come from the pinned compiler.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Test proof generation on Testnet or a local network
&lt;/h2&gt;

&lt;p&gt;Unit tests are necessary, but they do not replace proof generation. Before Mainnet deployment, run a full flow that generates ZK proofs, submits transactions, waits for finalization, and reads public state.&lt;/p&gt;

&lt;p&gt;Start a local proof server with the version from the compatibility matrix:&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; 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;The proof server listens on port &lt;code&gt;6300&lt;/code&gt;. Keep it running while deploying or interacting with your DApp. Use a local proof server or a controlled endpoint for sensitive test data.&lt;/p&gt;

&lt;p&gt;For final pre-production testing, use Preprod. Preview is suitable for early development and experimentation. Preprod is designed for final testing before Mainnet deployment. Mainnet is the production network.&lt;/p&gt;

&lt;p&gt;Your proof-generation test should cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy the smart contract.&lt;/li&gt;
&lt;li&gt;Submit a successful transaction for each exported state-changing circuit.&lt;/li&gt;
&lt;li&gt;Wait for the transaction receipt and assert the applied status.&lt;/li&gt;
&lt;li&gt;Query the public ledger state and confirm only approved fields changed.&lt;/li&gt;
&lt;li&gt;Submit a negative transaction for each authorization and replay boundary.&lt;/li&gt;
&lt;li&gt;Restart the DApp process and confirm private-state persistence works.&lt;/li&gt;
&lt;li&gt;Restart the proof server and rerun one representative transaction.&lt;/li&gt;
&lt;li&gt;Record proof-generation duration for the largest circuit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pass this section only when a real proof-generation flow succeeds against the environment you plan to use for deployment testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI checklist
&lt;/h2&gt;

&lt;p&gt;Add this checklist to pull requests that modify Compact source, TypeScript witnesses, generated smart contract code, package versions, Docker images, provider configuration, or deployment scripts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Midnight deployment security checklist&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [ ] &lt;span class="sb"&gt;`compact format --check src/`&lt;/span&gt; passes.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="sb"&gt;`compact fixup --check src/`&lt;/span&gt; passes.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Compact source recompiles from a clean generated directory.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] TypeScript type checking passes.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Unit tests cover success paths and negative paths.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Every &lt;span class="sb"&gt;`disclose()`&lt;/span&gt; call has a documented public reason.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] No raw private data appears in exported ledger fields.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="sb"&gt;`ownPublicKey()`&lt;/span&gt; is absent from caller verification.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Replay protection uses a nonce, nullifier, counter, or round.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Witness return types match generated TypeScript types.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Malicious witness tests fail safely.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Versions match the compatibility matrix.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Proof generation succeeds on local network, Preview, or Preprod.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Public state after proof submission matches the exported ledger review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The compiler reports a missing disclosure
&lt;/h3&gt;

&lt;p&gt;Do not wrap the earliest private value in &lt;code&gt;disclose()&lt;/code&gt; just to silence the compiler. Move &lt;code&gt;disclose()&lt;/code&gt; to the public assignment or exported return. Then document the disclosure.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ownPublicKey()&lt;/code&gt; appears in authorization
&lt;/h3&gt;

&lt;p&gt;Treat it as a blocker. Replace it with a commitment derived from &lt;code&gt;localSecretKey()&lt;/code&gt; or another verified private input. Bind that commitment to a domain separator and replay boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  A replay test succeeds twice
&lt;/h3&gt;

&lt;p&gt;Find the missing uniqueness boundary. Add a counter, round, nonce, or nullifier to the commitment derivation. Update or claim it after a successful call.&lt;/p&gt;

&lt;h3&gt;
  
  
  A sealed field fails compilation
&lt;/h3&gt;

&lt;p&gt;Check whether an exported circuit modifies a &lt;code&gt;sealed ledger&lt;/code&gt; field. Sealed fields belong to initialization logic. Use unsealed fields only when post-deployment mutation is required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proof generation fails but unit tests pass
&lt;/h3&gt;

&lt;p&gt;Check version compatibility first. Confirm the Compact compiler, generated files, Compact runtime, Ledger version, Midnight.js packages, and proof server image target the same release. Then inspect circuit complexity and witness return types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Port &lt;code&gt;6300&lt;/code&gt; is unavailable
&lt;/h3&gt;

&lt;p&gt;Stop the existing process or container using the port. If you remap the host port, update the DApp and Lace proof server configuration to match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment decision
&lt;/h2&gt;

&lt;p&gt;Deploy only when the checklist produces evidence: command output, test results, source links, generated metadata, transaction hashes, and public ledger snapshots. A passing DApp has a clear disclosure policy, safe authorization, replay boundaries, reviewed public state, correct witnesses, compatible versions, and proof-generation coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;After this checklist passes, publish the review notes with the release candidate. Include the Compact compiler version, Ledger version, proof server version, target environment, transaction hashes from testing, and the exported ledger review. Keep the checklist in the repository so future feature work cannot bypass it.&lt;/p&gt;

</description>
      <category>midnightfordevs</category>
    </item>
    <item>
      <title>Midnight vs Other Privacy Chains: Architecture Comparison for Developers</title>
      <dc:creator>Arihant Agarwal</dc:creator>
      <pubDate>Sun, 10 May 2026 23:19:18 +0000</pubDate>
      <link>https://dev.to/arihant_agarwal_359eb9f57/midnight-vs-other-privacy-chains-architecture-comparison-for-developers-5179</link>
      <guid>https://dev.to/arihant_agarwal_359eb9f57/midnight-vs-other-privacy-chains-architecture-comparison-for-developers-5179</guid>
      <description>&lt;p&gt;Privacy-focused chains do not solve the same problem. Some focus on shielded payments. Some focus on private program execution. Some focus on recursive proofs and cheap verification. This tutorial compares Midnight, Aztec, Aleo, Mina, and Zcash from a developer point of view: what you write, where state lives, what the chain verifies, and where engineering friction appears.&lt;/p&gt;

&lt;p&gt;This guide is for developers and Web3 enthusiasts who understand basic smart contract concepts and want a practical comparison of privacy-preserving stacks.&lt;/p&gt;

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

&lt;p&gt;You should know account and UTXO state models, zero-knowledge proof basics, and the lifecycle of deploying a smart contract or DApp.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this comparison checks
&lt;/h2&gt;

&lt;p&gt;Use three questions to compare these systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language design:&lt;/strong&gt; How do you express public values, private values, assertions, and proof-generating logic?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State model:&lt;/strong&gt; Does state live in accounts, notes, records, local private state, public mappings, or commitment trees?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy model:&lt;/strong&gt; What does the chain hide, what does it reveal, and how does a developer control disclosure?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Midnight sits between programmable smart contracts and shielded asset movement. It uses Compact for smart contracts, ZK circuits for correctness, Kachina for data-protecting smart contract execution, Zswap for shielded multi-asset swaps, and a dual ledger approach that combines UTXO-style ledger tokens with account-style contract tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  A current Midnight baseline
&lt;/h2&gt;

&lt;p&gt;Start with the smallest useful Compact example. The current Midnight Hello World tutorial uses Compact language version &lt;code&gt;&amp;gt;= 0.23&lt;/code&gt; and shows the main privacy boundary in one line: a private circuit parameter becomes public only through &lt;code&gt;disclose()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.23;

export ledger message: Opaque&amp;lt;"string"&amp;gt;;

export circuit storeMessage(newMessage: Opaque&amp;lt;"string"&amp;gt;): [] {
  message = disclose(newMessage);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ledger&lt;/code&gt; declaration creates on-chain state. Circuit parameters are private by default. &lt;code&gt;disclose(newMessage)&lt;/code&gt; makes the value available for public storage. That explicit boundary is the key Compact habit: do not let private inputs become public by accident.&lt;/p&gt;

&lt;p&gt;A typical setup flow follows the official example repository and compiler workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/midnightntwrk/example-hello-world.git
&lt;span class="nb"&gt;cd &lt;/span&gt;example-hello-world
yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;contracts
compact compile hello-world.compact managed/hello-world.compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For DApp interaction, you also run a proof server. The proof server generates ZK proofs for circuit calls before the transaction reaches the network.&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; 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;That workflow shows Midnight's shape. You write Compact. The compiler validates the smart contract, emits ZK artifacts, and generates TypeScript-facing APIs. Midnight.js connects the compiled smart contract to providers for public data, private state, ZK artifacts, proof generation, wallet interaction, transaction submission, and logging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compact source
  -&amp;gt; compiler validation
  -&amp;gt; JavaScript circuit implementation
  -&amp;gt; TypeScript declarations
  -&amp;gt; ZK intermediate representation and keys
  -&amp;gt; Midnight.js providers
  -&amp;gt; proof server
  -&amp;gt; submitted transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Midnight: Compact, Kachina, Zswap, and the dual ledger
&lt;/h2&gt;

&lt;p&gt;Midnight's developer model starts with Compact. Compact is not Solidity with a privacy library attached. It is a smart contract language that compiles to ZK circuits used to prove correctness of interactions with the ledger. That matters because you write application logic with proof generation in mind from the start.&lt;/p&gt;

&lt;p&gt;Kachina explains the contract privacy model. Public state lives on-chain. Private state stays local to the user or application. A ZK proof connects the two by proving that a private state transition justifies a public state update. Validators do not need to learn the private witness. They verify the proof and the public part of the transaction.&lt;/p&gt;

&lt;p&gt;That model feels different from an EVM account update. In a Solidity contract, the chain sees the call data unless you add a separate privacy mechanism. In Midnight, the default design pushes sensitive inputs into local execution and proof generation. You still need to decide what becomes public. The compiler does not remove the need for data classification. It makes the boundary explicit.&lt;/p&gt;

&lt;p&gt;Zswap handles the asset side. It is a data-protecting transaction scheme for multi-asset atomic swaps. It draws from Zerocash and Zcash Sapling ideas, uses ZK proofs and commitments, and supports non-interactive merging of transactions. For developers, this means Midnight is not only about hiding smart contract inputs. It also gives a native path for shielded asset exchange and DeFi-style flows where pre-trade visibility can leak value.&lt;/p&gt;

&lt;p&gt;Midnight also uses a dual ledger approach. Ledger tokens use a UTXO model. Contract tokens use an account-style model inside Compact smart contracts. That gives you two design paths. Use ledger tokens when you need shielded, UTXO-style asset movement. Use contract tokens when your application needs smart contract controlled state. The trade-off is that you must choose the right token model early. Migrating an application from one state assumption to another can change wallet UX, indexing, and proof flow.&lt;/p&gt;

&lt;p&gt;The main strength is coherence. Compact, Midnight.js, the proof server, Kachina, and Zswap target the same application style: privacy-preserving DApps with explicit disclosure. The main friction is operational. You manage proof generation, private state, public data queries, network compatibility, and toolchain versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aztec: Noir, Aztec.nr, PXE, and notes
&lt;/h2&gt;

&lt;p&gt;Aztec is a privacy-first Ethereum Layer 2 that combines private and public execution. Developers write smart contracts in Noir through Aztec.nr. Private functions execute client-side and produce proofs. Public functions execute in the Aztec Virtual Machine. This split gives Aztec a strong programming model for applications that need both confidential user actions and public settlement.&lt;/p&gt;

&lt;p&gt;Aztec's state model centers on notes. A note is an encrypted piece of private state. The protocol stores note hashes and uses nullifiers to prevent double spending. Public state sits in a public data tree. Private state behaves like a UTXO model: you create notes, consume notes, and reveal nullifiers when notes are spent. The user discovers and decrypts relevant notes through local tooling.&lt;/p&gt;

&lt;p&gt;The PXE, or private execution environment, is central to the developer experience. It stores secrets, synchronizes relevant notes, prepares private function execution, and generates proofs before transaction submission. That gives developers a clear privacy boundary: private inputs stay in the PXE. It also creates a local-state dependency. If note discovery or PXE synchronization fails, the application can feel broken even when the chain is healthy.&lt;/p&gt;

&lt;p&gt;Noir itself is broader than Aztec. It compiles to an intermediate representation that can target proving backends. Aztec.nr adds the blockchain-specific layer: macros, storage abstractions, contract patterns, and execution conventions. If you already think in ZK circuits, Noir is attractive because it exposes a general ZK programming language. If you come from account-based smart contracts, the note lifecycle is the larger adjustment.&lt;/p&gt;

&lt;p&gt;A minimal Aztec.nr private counter shows the model. The private function updates note-backed state for an owner, while the utility function reads local private state through the PXE.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;aztec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;macros&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;aztec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[aztec]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;aztec&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
        &lt;span class="nn"&gt;macros&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;external&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initializer&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nn"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;message_delivery&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MessageDelivery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;address&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AztecAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;state_vars&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Owned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;balance_set&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BalanceSet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;#[storage]&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;counters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Owned&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BalanceSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[initializer]&lt;/span&gt;
    &lt;span class="nd"&gt;#[external(&lt;/span&gt;&lt;span class="s"&gt;"private"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AztecAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.storage.counters&lt;/span&gt;&lt;span class="nf"&gt;.at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MessageDelivery&lt;/span&gt;&lt;span class="py"&gt;.ONCHAIN_CONSTRAINED&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="nd"&gt;#[external(&lt;/span&gt;&lt;span class="s"&gt;"private"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AztecAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.storage.counters&lt;/span&gt;&lt;span class="nf"&gt;.at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MessageDelivery&lt;/span&gt;&lt;span class="py"&gt;.ONCHAIN_CONSTRAINED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[external(&lt;/span&gt;&lt;span class="s"&gt;"utility"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;unconstrained&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AztecAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="nb"&gt;u128&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.storage.counters&lt;/span&gt;&lt;span class="nf"&gt;.at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.balance_of&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aztec is strong when an application needs Ethereum-adjacent settlement, programmable private actions, and a clean split between private and public functions. The cost is complexity around notes, PXE state, asynchronous execution, and the mental shift from updating contract storage to consuming and creating private commitments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aleo: Leo, records, transitions, and mappings
&lt;/h2&gt;

&lt;p&gt;Aleo uses Leo as its developer-facing language. Leo looks like a domain-specific language for ZK applications rather than a general smart contract language. It gives developers records for private state and mappings for public state.&lt;/p&gt;

&lt;p&gt;A record is Aleo's private state container. It can hold arbitrary application data. Records are encrypted on-chain, owned by an address, and consumed when used. A transition can consume old records and create new records. This mirrors the UTXO lifecycle: state changes by spending old objects and producing new objects, not by mutating a global account balance in place.&lt;/p&gt;

&lt;p&gt;Public state uses mappings. Mappings are visible on-chain and suit counters, registries, configuration, and other shared values. Leo also supports finalizers for public state updates after private execution. The result is a hybrid model. Private data flows through records. Public coordination flows through mappings.&lt;/p&gt;

&lt;p&gt;In Leo, the split is visible in the syntax: records carry private state, mappings carry public state, and an async finalizer updates the mapping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;program private_points.aleo {
    mapping public_points: address =&amp;gt; u64;

    record Points {
        owner: address,
        amount: u64,
    }

    transition mint_private(receiver: address, amount: u64) -&amp;gt; Points {
        return Points {
            owner: receiver,
            amount: amount,
        };
    }

    async transition publish(points: Points) -&amp;gt; Future {
        return finalize_publish(points.owner, points.amount);
    }

    async function finalize_publish(owner: address, amount: u64) {
        let current: u64 = Mapping::get_or_use(public_points, owner, 0u64);
        Mapping::set(public_points, owner, current + amount);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aleo fits applications whose state maps to private record ownership. The hard parts are shared mutable state, coordination, indexing, and recovery because each user depends on finding, decrypting, and spending the right records.&lt;/p&gt;

&lt;p&gt;Compared with Midnight, Aleo puts more emphasis on records as the unit of private program state. Midnight puts more emphasis on the relationship between local private state, public ledger state, and smart contract calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mina: o1js, zkApps, and recursive proofs
&lt;/h2&gt;

&lt;p&gt;Mina approaches privacy and scalability from a different angle. Its signature feature is recursive proof composition. The chain itself uses recursive proofs to keep verification small. Developers build zkApps with o1js, a TypeScript library for writing ZK circuits and smart contracts.&lt;/p&gt;

&lt;p&gt;The developer experience is familiar if you already use TypeScript. You define a smart contract class, add methods, use o1js field types, and generate proofs locally. The network verifies the proof and applies account updates. On-chain state is intentionally small. A zkApp account has a limited number of on-chain fields, so applications often keep larger state off-chain and commit to it with hashes or Merkle roots.&lt;/p&gt;

&lt;p&gt;Privacy in Mina is about what the proof hides. Method parameters and computation can remain private, while the public account update reveals the verified state change. Mina is less natural for rich native shielded asset flows or large private state directly represented on-chain.&lt;/p&gt;

&lt;p&gt;Mina's best developer story is proof composition. You can build a proof of a computation, then use recursion to fold proofs together or verify one proof inside another. For identity, games, compliance checks, and verifiable compute, this can be more important than a full shielded transaction system. The trade-off is that you design around small public state and off-chain data availability.&lt;/p&gt;

&lt;p&gt;An o1js contract looks like TypeScript, but the method body builds constraints. Here, the public state stores only a commitment. The &lt;code&gt;secret&lt;/code&gt; argument stays private inside the proof.&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;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Poseidon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SmartContract&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="nx"&gt;method&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o1js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HashCounter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SmartContract&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Poseidon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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="nd"&gt;method&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;incrementSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&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;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requireEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;Poseidon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Poseidon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared with Midnight, Mina gives you a TypeScript proving environment and a strong recursive proof story. Midnight gives you a domain-specific smart contract language, a proof server workflow, and native shielded asset architecture through Zswap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zcash Sapling: shielded payments, not general DApps
&lt;/h2&gt;

&lt;p&gt;Zcash Sapling is the reference point for shielded payment privacy. Sapling improved shielded transaction efficiency, introduced Sapling shielded addresses, and supports viewing keys that let a holder disclose transaction details without giving spend authority.&lt;/p&gt;

&lt;p&gt;The Sapling state model uses note commitments and nullifiers. A shielded output creates a note commitment. A spend reveals a nullifier so the network can reject double spends without learning which note was spent. The protocol can hide sender, receiver, amount, and memo data for shielded transfers, while fees and some transaction metadata remain visible.&lt;/p&gt;

&lt;p&gt;For developers, Zcash is not a general-purpose privacy-preserving smart contract platform. It is a payment protocol and wallet ecosystem. You build wallets, exchanges, payment flows, custody tools, compliance flows, and viewing-key workflows. You do not write arbitrary DApps that update application state through smart contract circuits.&lt;/p&gt;

&lt;p&gt;The developer surface is therefore RPC and wallet integration. A Sapling-oriented flow creates an account, derives a shielded receiver, sends with a privacy policy, and checks the async operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zcash-cli z_getnewaccount
zcash-cli z_getaddressforaccount ACCOUNT_NUMBER &lt;span class="s1"&gt;'["sapling"]'&lt;/span&gt;
zcash-cli z_sendmany &lt;span class="s2"&gt;"FROM_SHIELDED_OR_UNIFIED_ADDRESS"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'[{"address":"RECIPIENT_SHIELDED_OR_UNIFIED_ADDRESS","amount":0.01,"memo":"48656c6c6f"}]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  10 null FullPrivacy
zcash-cli z_getoperationstatus &lt;span class="s1"&gt;'["OPERATION_ID"]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes Zcash a clean comparison point. It shows mature shielded payment privacy, but not programmable private application logic. If the product is a privacy-preserving application with custom logic, you need one of the programmable stacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer experience comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Language design&lt;/th&gt;
&lt;th&gt;State model&lt;/th&gt;
&lt;th&gt;Privacy model&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Main friction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Midnight&lt;/td&gt;
&lt;td&gt;Compact smart contracts compile to ZK circuits and TypeScript-facing artifacts.&lt;/td&gt;
&lt;td&gt;Public ledger state, local private state, UTXO-style ledger tokens, and account-style contract tokens.&lt;/td&gt;
&lt;td&gt;ZK proofs connect local private state to public updates. Zswap supports shielded multi-asset swaps.&lt;/td&gt;
&lt;td&gt;Privacy-preserving DApps that need smart contract logic and shielded assets.&lt;/td&gt;
&lt;td&gt;Toolchain compatibility, proof server setup, private state handling, and token model choice.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aztec&lt;/td&gt;
&lt;td&gt;Noir plus Aztec.nr for private and public smart contract functions.&lt;/td&gt;
&lt;td&gt;Public data tree plus encrypted notes and nullifiers.&lt;/td&gt;
&lt;td&gt;Private functions run client-side through PXE. Public functions run in the AVM.&lt;/td&gt;
&lt;td&gt;Ethereum-adjacent applications with private actions and public settlement.&lt;/td&gt;
&lt;td&gt;Note discovery, PXE sync, asynchronous private/public flows, and non-EVM design.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aleo&lt;/td&gt;
&lt;td&gt;Leo programs with transitions, records, mappings, and finalizers.&lt;/td&gt;
&lt;td&gt;Private records and public mappings.&lt;/td&gt;
&lt;td&gt;Records are encrypted and spent like private UTXOs. Public mappings support shared state.&lt;/td&gt;
&lt;td&gt;Private-by-default applications where user-owned records are natural.&lt;/td&gt;
&lt;td&gt;Record lifecycle management, shared state coordination, and indexing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mina&lt;/td&gt;
&lt;td&gt;o1js TypeScript smart contracts and ZK programs.&lt;/td&gt;
&lt;td&gt;Small on-chain account state, off-chain data, and committed roots.&lt;/td&gt;
&lt;td&gt;Proofs hide private inputs and computation while public account updates settle on-chain.&lt;/td&gt;
&lt;td&gt;Recursive proof applications and succinct verification of off-chain compute.&lt;/td&gt;
&lt;td&gt;Limited on-chain state, proof design, and off-chain data management.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zcash Sapling&lt;/td&gt;
&lt;td&gt;Protocol-level shielded payment logic, not a smart contract language.&lt;/td&gt;
&lt;td&gt;Note commitments, nullifiers, shielded addresses, and transparent addresses.&lt;/td&gt;
&lt;td&gt;Shielded transfers hide sender, receiver, amount, and memo data. Viewing keys support disclosure.&lt;/td&gt;
&lt;td&gt;Payments, wallets, custody, and shielded transfer workflows.&lt;/td&gt;
&lt;td&gt;No general smart contract layer for application logic.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;p&gt;Choose Midnight when your DApp needs privacy-preserving smart contract logic and shielded asset movement in the same architecture. Compact gives you explicit disclosure control. Zswap gives you shielded multi-asset exchange. The dual ledger gives you a choice between UTXO-style ledger tokens and account-style smart contract tokens.&lt;/p&gt;

&lt;p&gt;Choose Aztec when your application benefits from Ethereum Layer 2 settlement and you can accept the note-based private state model. Aztec is a strong fit for applications that need both private user actions and public composability.&lt;/p&gt;

&lt;p&gt;Choose Aleo when your product state maps cleanly to private records. It works well when users own private objects, spend them, and receive new ones as the application evolves.&lt;/p&gt;

&lt;p&gt;Choose Mina when the application centers on proving off-chain computation and composing proofs. Mina is less about shielded asset flows and more about succinct verification.&lt;/p&gt;

&lt;p&gt;Choose Zcash when the requirement is shielded payment privacy rather than programmable DApp logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting common mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Version mismatch:&lt;/strong&gt; Compact examples depend on language and toolchain versions. Match the &lt;code&gt;pragma language_version&lt;/code&gt; directive to the installed Compact toolchain and the current Midnight compatibility matrix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proof server unavailable:&lt;/strong&gt; Midnight DApp calls that require proofs need a reachable proof server or proof provider. Check the host, port, Docker status, and ZK artifact path before debugging wallet code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accidental disclosure:&lt;/strong&gt; Treat &lt;code&gt;disclose()&lt;/code&gt; as a security review point. Every disclosed value becomes part of the public logic. If a value should stay private, keep it in the witness path or local private state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong token model:&lt;/strong&gt; Ledger tokens and contract tokens do not give the same developer experience. Use the UTXO-style path for shielded asset movement and the account-style path for smart contract managed tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lost local state:&lt;/strong&gt; Aztec notes, Aleo records, and Midnight private state all depend on local discovery or storage. Plan backup, recovery, and indexing before users hold value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overusing ZK:&lt;/strong&gt; A proof hides inputs and computation, but it does not hide everything. Timing, fees, public state changes, nullifiers, commitments, and application-level messages can still leak information.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/compact" rel="noopener noreferrer"&gt;Midnight Compact documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/getting-started/hello-world" rel="noopener noreferrer"&gt;Midnight Hello World tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/concepts/kachina" rel="noopener noreferrer"&gt;Midnight Kachina documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/concepts/zswap" rel="noopener noreferrer"&gt;Midnight Zswap documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/concepts/ledgers" rel="noopener noreferrer"&gt;Midnight ledgers documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/sdks/official/midnight-js" rel="noopener noreferrer"&gt;Midnight.js documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aztec.network/" rel="noopener noreferrer"&gt;Aztec documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aztec.network/developers/docs/tutorials/contract_tutorials/counter_contract" rel="noopener noreferrer"&gt;Aztec counter contract tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aztec.network/developers/docs/foundational-topics/state_management" rel="noopener noreferrer"&gt;Aztec state model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aztec.network/developers/docs/foundational-topics/pxe" rel="noopener noreferrer"&gt;Aztec PXE documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aztec.network/developers/docs/aztec-nr" rel="noopener noreferrer"&gt;Aztec.nr documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://noir-lang.org/docs" rel="noopener noreferrer"&gt;Noir documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.aleo.org/guides/solidity-to-leo/migration-guide/" rel="noopener noreferrer"&gt;Aleo Solidity-to-Leo migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.aleo.org/concepts/fundamentals/public_private/" rel="noopener noreferrer"&gt;Aleo public and private state documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.aleo.org/concepts/fundamentals/records/" rel="noopener noreferrer"&gt;Aleo records documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.minaprotocol.com/zkapps/writing-a-zkapp/introduction-to-zkapps/smart-contracts" rel="noopener noreferrer"&gt;Mina smart contract documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.minaprotocol.com/zkapps/o1js/recursion" rel="noopener noreferrer"&gt;Mina recursion documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zcash.readthedocs.io/en/master/rtd_pages/addresses.html" rel="noopener noreferrer"&gt;Zcash addresses and value pools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zcash.github.io/rpc/z_getnewaccount.html" rel="noopener noreferrer"&gt;Zcash new account RPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zcash.github.io/rpc/z_getaddressforaccount.html" rel="noopener noreferrer"&gt;Zcash account address RPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zcash.github.io/rpc/z_sendmany.html" rel="noopener noreferrer"&gt;Zcash shielded send RPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://z.cash/upgrade/sapling/" rel="noopener noreferrer"&gt;Zcash Sapling network upgrade&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>midnightfordevs</category>
    </item>
    <item>
      <title>Getting NIGHT Tokens: Exchanges, Bridging &amp; Wallet Funding on Mainnet: An Overview</title>
      <dc:creator>Arihant Agarwal</dc:creator>
      <pubDate>Sun, 10 May 2026 22:55:11 +0000</pubDate>
      <link>https://dev.to/arihant_agarwal_359eb9f57/getting-night-tokens-exchanges-bridging-wallet-funding-on-mainnet-an-overview-18e6</link>
      <guid>https://dev.to/arihant_agarwal_359eb9f57/getting-night-tokens-exchanges-bridging-wallet-funding-on-mainnet-an-overview-18e6</guid>
      <description>&lt;p&gt;NIGHT is the unshielded native and governance token of Midnight. It gives users and developers access to the network resource model that powers transactions on Midnight. This tutorial explains where you can acquire NIGHT, how to withdraw it safely, how to prepare a Midnight-compatible wallet, and what the Cardano-to-Midnight bridge supports today.&lt;/p&gt;

&lt;p&gt;Use this guide if you want to buy NIGHT, move it into self-custody, generate DUST, or prepare for Midnight Mainnet activity.It is best suited for: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users who want to buy and hold NIGHT.&lt;/li&gt;
&lt;li&gt;Developers who need NIGHT and DUST for testing or Mainnet usage.&lt;/li&gt;
&lt;li&gt;Builders who need to explain the current NIGHT wallet and bridge flow to their users.&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;ul&gt;
&lt;li&gt;An account on an exchange that lists NIGHT, or access to a Cardano DEX.&lt;/li&gt;
&lt;li&gt;A Cardano wallet that supports Cardano Native Assets.&lt;/li&gt;
&lt;li&gt;A Midnight-compatible wallet, such as Lace, if you want to use Midnight Mainnet.&lt;/li&gt;
&lt;li&gt;A small amount of ADA in your Cardano wallet for Cardano transaction fees.&lt;/li&gt;
&lt;li&gt;The official NIGHT policy ID on Cardano:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0691b2fecca1ac4f53cb6dfb00b7013e561d1f34403b957cbb5af1fa4e49474854
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tutorial contains no Compact code. It only covers token acquisition, wallet preparation, withdrawal checks, and bridge status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand NIGHT and DUST
&lt;/h2&gt;

&lt;p&gt;Midnight uses a dual-component token model.&lt;/p&gt;

&lt;p&gt;NIGHT is the public token. It is unshielded, visible on-chain, and used for governance, network security, and DUST generation. DUST is the shielded network resource that pays for transaction capacity on Midnight. You spend DUST to use Midnight, but you do not buy it as a normal transferable token.&lt;/p&gt;

&lt;p&gt;The difference matters when you use an exchange. Exchanges list NIGHT. They do not list DUST as a token you can buy, withdraw, or transfer. DUST is generated from NIGHT balances and is used as a consumable resource for transactions and smart contract execution.&lt;/p&gt;

&lt;p&gt;Midnight documentation describes NIGHT as a token that exists across Cardano and Midnight. On Cardano, NIGHT is a Cardano Native Asset. On Midnight, NIGHT is a native token on Midnight. The protocol keeps supply consistent by locking NIGHT on one chain when it is unlocked on the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to get NIGHT
&lt;/h2&gt;

&lt;p&gt;You can acquire NIGHT through three main paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Buy NIGHT on a centralized exchange
&lt;/h2&gt;

&lt;p&gt;A centralized exchange is the simplest path for most users. You create an account, complete any required identity checks, deposit funds, buy NIGHT, and withdraw to self-custody when withdrawals are available.&lt;/p&gt;

&lt;p&gt;The official Midnight NIGHT page lists exchange partners that include:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exchange&lt;/th&gt;
&lt;th&gt;Typical market to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Binance&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NIGHT/USDT&lt;/code&gt; or supported local pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitpanda&lt;/td&gt;
&lt;td&gt;App-based NIGHT markets where available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitrue&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NIGHT/USDT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bybit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NIGHT/USDT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NIGHT/USDT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTX&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NIGHT/USDT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kraken&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NIGHT/USD&lt;/code&gt; or other supported fiat pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KuCoin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NIGHT/USDT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LBank&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NIGHT/USDT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEXC&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NIGHT/USDT&lt;/code&gt; or &lt;code&gt;NIGHT/USDC&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OKX&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NIGHT/USDT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eToro&lt;/td&gt;
&lt;td&gt;App-based NIGHT markets where available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Market availability changes by region, account type, and exchange policy. Before you buy, open the exchange's deposit and withdrawal page for NIGHT. Confirm that the exchange supports withdrawals, not just trading. Some venues list a token before withdrawals are available, and some pause withdrawals during maintenance.&lt;/p&gt;

&lt;p&gt;A good exchange choice is not only the exchange with the lowest trading fee. It is the venue that supports your region, has enough liquidity, allows withdrawals, and clearly shows the withdrawal network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Swap for NIGHT on Cardano
&lt;/h2&gt;

&lt;p&gt;You can also acquire NIGHT through Cardano DEX liquidity when routes are available. This path keeps you in self-custody from the start, but it requires careful asset verification.&lt;/p&gt;

&lt;p&gt;Before you swap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the DEX or aggregator you plan to use.&lt;/li&gt;
&lt;li&gt;Search for NIGHT by policy ID, not only by token name.&lt;/li&gt;
&lt;li&gt;Confirm the policy ID is:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0691b2fecca1ac4f53cb6dfb00b7013e561d1f34403b957cbb5af1fa4e49474854
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check the route, price impact, and slippage.&lt;/li&gt;
&lt;li&gt;Confirm the transaction in your Cardano wallet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not rely on the ticker alone. Cardano allows different assets to use similar names. The policy ID is the reliable identifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: Redeem previously claimed NIGHT
&lt;/h2&gt;

&lt;p&gt;Some users received NIGHT through distribution programs such as Glacier Drop, Scavenger Mine, or later redemption phases. If you participated in one of these programs, use the official redemption flow and follow the instructions for your claim type.&lt;/p&gt;

&lt;p&gt;The official NIGHT page states that the total supply is &lt;code&gt;24,000,000,000 NIGHT&lt;/code&gt; and that the distribution includes a thawing period. If you redeem distribution tokens, check whether your balance is available immediately or subject to the thawing schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the right wallet
&lt;/h2&gt;

&lt;p&gt;You need to separate two wallet tasks.&lt;/p&gt;

&lt;p&gt;The first task is receiving NIGHT from an exchange or DEX. For that, use a Cardano wallet that supports Cardano Native Assets if the withdrawal network is Cardano. The address must be a Cardano wallet address.&lt;/p&gt;

&lt;p&gt;The second task is using Midnight Mainnet. For that, use a Midnight-compatible wallet. Lace supports Midnight Mainnet inside the Lace extension and lets you manage public and shielded Midnight assets, generate DUST, and connect to Midnight DApps.&lt;/p&gt;

&lt;p&gt;Do not paste a Midnight wallet address into a Cardano withdrawal form. Do not paste a Cardano wallet address into a Midnight-only transfer form. The address must match the network used by the sender.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up a Midnight wallet in Lace
&lt;/h2&gt;

&lt;p&gt;Use this flow if you want to prepare a Midnight wallet in Lace.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install or update the Lace browser extension.&lt;/li&gt;
&lt;li&gt;Open Lace.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Create new wallet&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose whether to reuse an existing recovery phrase or create a new one.&lt;/li&gt;
&lt;li&gt;Store the recovery phrase offline.&lt;/li&gt;
&lt;li&gt;Re-enter the recovery phrase if Lace asks you to confirm it.&lt;/li&gt;
&lt;li&gt;Name the wallet and set a password.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Mainnet&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose the proof server option you want to use.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Enter wallet&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A proof server generates ZK proofs for Midnight transactions. Lace support explains that you can run a local proof server or connect to a remote service. Use the option that matches your security needs and technical comfort level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Withdraw NIGHT from an exchange
&lt;/h2&gt;

&lt;p&gt;Follow these steps when your exchange supports NIGHT withdrawals on Cardano.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Confirm the withdrawal network
&lt;/h2&gt;

&lt;p&gt;Open the NIGHT withdrawal page on the exchange and check the network field. If the exchange offers Cardano as the withdrawal network, prepare a Cardano receiving address.&lt;/p&gt;

&lt;p&gt;If the exchange offers Midnight Mainnet as a withdrawal network, confirm that your receiving wallet supports Midnight Mainnet and that the address format matches the exchange instructions. Do not assume Midnight withdrawals exist unless the exchange shows the network explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Copy a Cardano receiving address
&lt;/h2&gt;

&lt;p&gt;Open your Cardano wallet and copy a fresh receiving address. Make sure the wallet can display Cardano Native Assets.&lt;/p&gt;

&lt;p&gt;Keep some ADA in the wallet. Cardano transactions require ADA for fees and may require minimum ADA values for token UTXOs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Send a small test withdrawal
&lt;/h2&gt;

&lt;p&gt;Start with a small amount of NIGHT. This costs an extra withdrawal fee, but it protects you from sending the full balance to the wrong network or an unsupported wallet.&lt;/p&gt;

&lt;p&gt;After the exchange processes the withdrawal, check your wallet. Confirm that the received asset uses the official NIGHT policy ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Withdraw the remaining balance
&lt;/h2&gt;

&lt;p&gt;After the test withdrawal arrives, send the remaining amount. Save the transaction hash and exchange withdrawal record.&lt;/p&gt;

&lt;p&gt;If your wallet does not update right away, use the transaction hash in a Cardano explorer and confirm the asset, destination address, and amount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Prepare for Midnight usage
&lt;/h2&gt;

&lt;p&gt;After NIGHT reaches your Cardano wallet, set up or open your Midnight wallet. To use Midnight transactions, you need DUST capacity. You can generate DUST by designating NIGHT to a Midnight address, or you can use a DApp that sponsors your transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand designation
&lt;/h2&gt;

&lt;p&gt;Designation associates NIGHT with a Midnight address so it can generate DUST for that address. Without designation, your NIGHT does not generate DUST for that Midnight wallet.&lt;/p&gt;

&lt;p&gt;Lace support describes two related actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Designating:&lt;/strong&gt; You use your NIGHT to generate DUST for a chosen Midnight wallet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receiving a designation:&lt;/strong&gt; Another party designates NIGHT so DUST is generated for your address. A DApp can use this pattern to sponsor user transactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Designation helps separate ownership from usage. For example, a developer can hold NIGHT and designate DUST capacity to users so the DApp feels free at the point of interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the Cardano-to-Midnight bridge
&lt;/h2&gt;

&lt;p&gt;Midnight maintains interoperability with Cardano through a native bridge model. For NIGHT, the core rule is supply consistency. NIGHT cannot be freely duplicated across both chains. When NIGHT is active on one chain, it is locked on the other.&lt;/p&gt;

&lt;p&gt;For users, the current experience has an important limitation. Lace support states that, for the moment, you cannot send NIGHT from a Cardano wallet or centralized exchange account directly to a Midnight wallet because Cardano and Midnight are different blockchains. Work is in progress to enable this feature.&lt;/p&gt;

&lt;p&gt;This means you should treat three actions as separate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Holding NIGHT on Cardano.&lt;/li&gt;
&lt;li&gt;Designating NIGHT so it generates DUST for a Midnight address.&lt;/li&gt;
&lt;li&gt;Moving NIGHT itself onto Midnight when supported by your wallet, exchange, or bridge interface.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not combine these actions in your head. A Cardano withdrawal is not the same as a Midnight transfer, and DUST generation is not the same as moving NIGHT.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does not exist yet
&lt;/h2&gt;

&lt;p&gt;Some user flows are still maturing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct Cardano or exchange transfers to a Midnight wallet are not the default
&lt;/h2&gt;

&lt;p&gt;The safest assumption is that exchange withdrawals use Cardano unless the exchange clearly shows Midnight Mainnet as a supported withdrawal network. Lace support still warns that direct transfers from a Cardano wallet or CEX account to a Midnight wallet are not available in that Lace flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  DUST is not a market token
&lt;/h2&gt;

&lt;p&gt;DUST is not a transferable token that you buy, sell, or withdraw from an exchange. It is a shielded resource for transaction capacity. It is generated from NIGHT, can decay, and is consumed when you use Midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wallet support is not uniform
&lt;/h2&gt;

&lt;p&gt;A wallet can support Cardano Native Assets without supporting Midnight Mainnet. A different wallet can support Midnight Mainnet but still depend on separate Cardano flows for NIGHT acquisition. Check the exact feature before you move funds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridge UX is still evolving
&lt;/h2&gt;

&lt;p&gt;Midnight documentation describes native bridge support, and Lace support describes current limits in the direct user flow. Until your wallet or exchange provides a clear bridge interface, avoid manual transfers between chains.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is coming
&lt;/h2&gt;

&lt;p&gt;Midnight Mainnet is live, and the ecosystem is moving through a phased rollout. The April 2026 network update describes the period after launch as an application and ecosystem expansion phase. Wallet support, DUST generation tools, DApp onboarding, and bridge UX are expected to improve as more services come online.&lt;/p&gt;

&lt;p&gt;For developers, the most important coming improvement is smoother onboarding. A DApp can sponsor user activity by designating DUST capacity to user addresses. This pattern lets users try a Midnight DApp without buying NIGHT first. It also lets builders hide token mechanics behind a clearer product experience.&lt;/p&gt;

&lt;p&gt;If you build on Midnight, design your onboarding around capability checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect whether the user has a Midnight wallet.&lt;/li&gt;
&lt;li&gt;Detect whether the wallet has DUST capacity.&lt;/li&gt;
&lt;li&gt;Explain when NIGHT designation is required.&lt;/li&gt;
&lt;li&gt;Sponsor the first transaction when your DApp supports it.&lt;/li&gt;
&lt;li&gt;Show clear recovery and proof server guidance.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The exchange lists NIGHT, but withdrawals are disabled
&lt;/h2&gt;

&lt;p&gt;Wait for withdrawals to open, or use a venue that supports withdrawals in your region. Do not buy on a venue if your goal is self-custody and the exchange does not provide a withdrawal path.&lt;/p&gt;

&lt;h2&gt;
  
  
  NIGHT does not appear in my wallet
&lt;/h2&gt;

&lt;p&gt;Check the transaction hash in a Cardano explorer. Confirm the withdrawal used the Cardano network, the destination address is correct, and the asset policy ID matches the official NIGHT policy ID.&lt;/p&gt;

&lt;p&gt;Some wallets hide new tokens by default. Check the wallet's token list settings if the transaction is confirmed but the asset does not appear in the main balance view.&lt;/p&gt;

&lt;h2&gt;
  
  
  I have NIGHT, but I cannot transact on Midnight
&lt;/h2&gt;

&lt;p&gt;You need DUST capacity for Midnight transactions. Designate NIGHT to your Midnight address, wait for DUST generation to begin, or use a DApp that sponsors your transaction capacity.&lt;/p&gt;

&lt;h2&gt;
  
  
  I copied the wrong address type
&lt;/h2&gt;

&lt;p&gt;Stop before you confirm the transaction. A Cardano withdrawal needs a Cardano wallet address. A Midnight transaction needs a Midnight wallet address. If you already submitted the withdrawal, contact the exchange or wallet support team and provide the transaction hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  I want to use Midnight without buying NIGHT
&lt;/h2&gt;

&lt;p&gt;You can use Midnight when a DApp sponsors your transaction capacity or when another party designates NIGHT to your Midnight address. This depends on the DApp and wallet support. Check the DApp's onboarding flow before you assume sponsorship is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security checklist
&lt;/h2&gt;

&lt;p&gt;Before you move funds, confirm each item:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The asset policy ID matches the official NIGHT policy ID.&lt;/li&gt;
&lt;li&gt;The withdrawal network matches the receiving wallet.&lt;/li&gt;
&lt;li&gt;The wallet supports the asset you are receiving.&lt;/li&gt;
&lt;li&gt;You have enough ADA for Cardano fees if you use Cardano.&lt;/li&gt;
&lt;li&gt;You saved the recovery phrase for your wallet.&lt;/li&gt;
&lt;li&gt;You tested the withdrawal with a small amount first.&lt;/li&gt;
&lt;li&gt;You saved the transaction hash.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Start with a small NIGHT purchase or swap. Withdraw to a Cardano wallet if the exchange uses Cardano as the withdrawal network. Verify the policy ID, then set up a Midnight wallet in Lace or another Midnight-compatible wallet.&lt;/p&gt;

&lt;p&gt;After that, learn how designation works. Designation is the bridge between holding NIGHT and having usable DUST capacity on Midnight. Once your wallet has DUST capacity, you can connect to Midnight DApps and start using Mainnet with fewer surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/getting-started" rel="noopener noreferrer"&gt;Midnight developer documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://midnight.network/night" rel="noopener noreferrer"&gt;Midnight NIGHT token page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://midnight.network/faq" rel="noopener noreferrer"&gt;Midnight FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://iohk.zendesk.com/hc/en-us/articles/56619263639961-Lace-Midnight-FAQs" rel="noopener noreferrer"&gt;Lace Midnight FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forum.midnight.network/" rel="noopener noreferrer"&gt;Midnight developer forum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.com/invite/midnightnetwork" rel="noopener noreferrer"&gt;Midnight Discord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>midnightfordevs</category>
    </item>
    <item>
      <title>Running a Midnight Node: Setup, Sync &amp; Monitoring: An In-depth Tutorial</title>
      <dc:creator>Arihant Agarwal</dc:creator>
      <pubDate>Sun, 10 May 2026 22:40:08 +0000</pubDate>
      <link>https://dev.to/arihant_agarwal_359eb9f57/running-a-midnight-node-setup-sync-monitoring-an-in-depth-tutorial-3ena</link>
      <guid>https://dev.to/arihant_agarwal_359eb9f57/running-a-midnight-node-setup-sync-monitoring-an-in-depth-tutorial-3ena</guid>
      <description>&lt;p&gt;Set up a Midnight full node from scratch, monitor sync, troubleshoot peers, and verify node health with Docker, RPC, and logs.&lt;/p&gt;

&lt;p&gt;This tutorial is for developers and node operators who want to run a Midnight full node, watch it sync, keep it healthy, and diagnose the common failure mode where a node stays on block &lt;code&gt;1&lt;/code&gt; while peers connect and disconnect.&lt;/p&gt;

&lt;p&gt;You do not need to run a proof server for a full node. A proof server generates ZK proofs for smart contract workflows. A full node syncs chain state, validates blocks and transactions, exposes local RPC if you enable it, and joins the P2P network.&lt;/p&gt;

&lt;p&gt;Since this is a hands-on tutorial, we will dive straight into it without much chatter. I'm sure you're here because you can't wait to deploy!&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;You will build a single-host operator setup with these services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Cardano node&lt;/li&gt;
&lt;li&gt;Cardano-db-sync&lt;/li&gt;
&lt;li&gt;PostgreSQL for Cardano-db-sync&lt;/li&gt;
&lt;li&gt;A Midnight full node&lt;/li&gt;
&lt;li&gt;Local RPC and Prometheus endpoints bound to &lt;code&gt;127.0.0.1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default path in this guide targets the Preview environment. Preview is best for early development and operator practice. Change the network variables for Preprod or Mainnet only after you check the current compatibility matrix.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Midnight full nodes fit into the network
&lt;/h2&gt;

&lt;p&gt;A Midnight node implements core protocol logic, manages P2P networking, and supports decentralized operation as a Cardano Partnerchain. It also runs the Midnight Ledger component, enforces protocol rules, maintains state integrity, discovers peers, establishes connections, and gossips state across the network.&lt;/p&gt;

&lt;p&gt;A full node and an archive node differ mainly in state retention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A full node syncs the blockchain, validates transactions, serves real-time state queries, and prunes older historical state. The default pruning window in the official docs is &lt;code&gt;256&lt;/code&gt; blocks.&lt;/li&gt;
&lt;li&gt;An archive node keeps the full block and state history. Use it for explorers, historical debugging, analytics, or services that need past state at arbitrary heights.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a full node unless you have a clear archive use case. Archive mode increases storage use and operational cost.&lt;/p&gt;

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

&lt;p&gt;Use a Linux host for production or serious operator testing. The commands below assume Ubuntu 22.04 LTS or Ubuntu 24.04 LTS.&lt;/p&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sudo&lt;/code&gt; access&lt;/li&gt;
&lt;li&gt;A stable internet connection&lt;/li&gt;
&lt;li&gt;Inbound TCP &lt;code&gt;30333&lt;/code&gt; open for P2P traffic&lt;/li&gt;
&lt;li&gt;Outbound HTTPS and P2P access&lt;/li&gt;
&lt;li&gt;Docker Engine and the Docker Compose plugin&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jq&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;openssl&lt;/code&gt;, &lt;code&gt;psql&lt;/code&gt;, and &lt;code&gt;nc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enough CPU, RAM, disk, and IOPS for Cardano-db-sync and the Midnight node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not use &lt;code&gt;latest&lt;/code&gt; image tags. Pin the node image for the network you run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the network and image version
&lt;/h2&gt;

&lt;p&gt;The values below reflect the public Midnight compatibility information available at the time of writing. Always check the release compatibility matrix before you start or upgrade.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Node image&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;td&gt;Early development and operator testing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;midnightntwrk/midnight-node:0.22.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preprod&lt;/td&gt;
&lt;td&gt;Final testing before Mainnet deployment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;midnightntwrk/midnight-node:0.22.2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mainnet&lt;/td&gt;
&lt;td&gt;Production network&lt;/td&gt;
&lt;td&gt;&lt;code&gt;midnightntwrk/midnight-node:0.22.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This tutorial uses Preview:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; midnight.env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
MIDNIGHT_NETWORK=preview
MIDNIGHT_NODE_VERSION=0.22.5
MIDNIGHT_NODE_IMAGE=midnightntwrk/midnight-node:0.22.5
CARDANO_NETWORK=preview
MIDNIGHT_BOOTNODE_1=/dns/bootnode-1.preview.midnight.network/tcp/30333/ws/p2p/12D3KooWK66i7dtGVNSwDh9tTeqov1q6LSdWsRLJvTyzTCaywYgK
MIDNIGHT_BOOTNODE_2=/dns/bootnode-2.preview.midnight.network/tcp/30333/ws/p2p/12D3KooWHqFfXFwb7WW4jwR8pr4BEf562v5M6c8K3CXAJq4Wx6ym
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Preprod, use this file instead:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; midnight.env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
MIDNIGHT_NETWORK=preprod
MIDNIGHT_NODE_VERSION=0.22.2
MIDNIGHT_NODE_IMAGE=midnightntwrk/midnight-node:0.22.2
CARDANO_NETWORK=preprod
MIDNIGHT_BOOTNODE_1=/dns/bootnode-1.preprod.midnight.network/tcp/30333/ws/p2p/12D3KooWQxxUgq7ndPfAaCFNbAxtcKYxrAzTxDfRGNktF75SxdX5
MIDNIGHT_BOOTNODE_2=/dns/bootnode-2.preprod.midnight.network/tcp/30333/ws/p2p/12D3KooWNrUBs22FfmgjqFMa9ZqKED2jnxwsXWw5E4q2XVwN35TJ
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mainnet operators should use the Mainnet image from the compatibility matrix and the Mainnet boot node configuration published for the current release. Do not reuse Preview or Preprod boot nodes on Mainnet.&lt;/p&gt;

&lt;p&gt;Some docs, older guides, and community posts may mention &lt;code&gt;testnet-02&lt;/code&gt; and the old &lt;code&gt;midnightnetwork/midnight-node&lt;/code&gt; image. Do not mix those values with Preview, Preprod, or Mainnet. A mismatched image, boot node, or chain spec is one of the fastest ways to get a node that starts but never syncs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Size the host
&lt;/h2&gt;

&lt;p&gt;Size the machine for the slowest component, which is usually Cardano-db-sync during initial catch-up. The official Cardano-db-sync guidance for Midnight lists these lower bounds:&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;Preview minimum&lt;/th&gt;
&lt;th&gt;Mainnet minimum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;4 cores&lt;/td&gt;
&lt;td&gt;4 cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;td&gt;32 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk for Cardano-db-sync&lt;/td&gt;
&lt;td&gt;40 GB SSD&lt;/td&gt;
&lt;td&gt;320 GB SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IOPS&lt;/td&gt;
&lt;td&gt;30,000 or better&lt;/td&gt;
&lt;td&gt;60,000 or better&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use these as the floor, not the target. A combined node host also needs space for the Midnight chain database, Docker layers, PostgreSQL growth, logs, and backups.&lt;/p&gt;

&lt;p&gt;Recommended starting sizes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;CPU&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;Storage&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;Preview full node&lt;/td&gt;
&lt;td&gt;4 vCPU&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;td&gt;150 GB SSD&lt;/td&gt;
&lt;td&gt;Good for development and testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preview full node with headroom&lt;/td&gt;
&lt;td&gt;8 vCPU&lt;/td&gt;
&lt;td&gt;32 GB&lt;/td&gt;
&lt;td&gt;250 GB NVMe&lt;/td&gt;
&lt;td&gt;Better for stable long-running service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mainnet full node&lt;/td&gt;
&lt;td&gt;8 vCPU&lt;/td&gt;
&lt;td&gt;32 GB or more&lt;/td&gt;
&lt;td&gt;500 GB NVMe&lt;/td&gt;
&lt;td&gt;Treat this as a practical floor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Archive node&lt;/td&gt;
&lt;td&gt;8 vCPU or more&lt;/td&gt;
&lt;td&gt;32 GB or more&lt;/td&gt;
&lt;td&gt;1 TB NVMe or more&lt;/td&gt;
&lt;td&gt;Monitor growth and plan expansion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Avoid HDD storage. A slow disk causes long sync times, peer churn, PostgreSQL stalls, and failed restarts after unclean shutdowns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install system packages
&lt;/h2&gt;

&lt;p&gt;Run the setup from a clean host:&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-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ca-certificates &lt;span class="se"&gt;\&lt;/span&gt;
  curl &lt;span class="se"&gt;\&lt;/span&gt;
  gnupg &lt;span class="se"&gt;\&lt;/span&gt;
  jq &lt;span class="se"&gt;\&lt;/span&gt;
  lsb-release &lt;span class="se"&gt;\&lt;/span&gt;
  netcat-openbsd &lt;span class="se"&gt;\&lt;/span&gt;
  openssl &lt;span class="se"&gt;\&lt;/span&gt;
  postgresql-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Docker Engine and the Docker Compose plugin:&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 install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg

&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION_CODENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  containerd.io &lt;span class="se"&gt;\&lt;/span&gt;
  docker-buildx-plugin &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce-cli &lt;span class="se"&gt;\&lt;/span&gt;
  docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allow your user to run Docker commands:&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;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker compose version
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the working directory
&lt;/h2&gt;

&lt;p&gt;Keep node files in a dedicated directory. This makes backups, resets, and audits easier.&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 mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/midnight-node
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /opt/midnight-node
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/midnight-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the PostgreSQL environment file. The password is random hex, so it is safe to source in shell commands without escaping issues. The database connection string uses the same password as the PostgreSQL service.&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;umask &lt;/span&gt;077
&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 24&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; postgres.env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_USER=midnight
POSTGRES_PASSWORD=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
POSTGRES_DB=cexplorer
DB_SYNC_POSTGRES_CONNECTION_STRING=psql://midnight:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;@postgres:5432/cexplorer
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load both environment files:&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;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; ./midnight.env
&lt;span class="nb"&gt;.&lt;/span&gt; ./postgres.env
&lt;span class="nb"&gt;set&lt;/span&gt; +a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start Cardano-db-sync
&lt;/h2&gt;

&lt;p&gt;Midnight nodes need a persistent connection to Cardano-db-sync. Start the Cardano side first and let it sync.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;cardano-db-sync.compose.yml&lt;/code&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="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cardano-db-sync.compose.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;'
networks:
  midnight-node-net:
    name: midnight-node-net

volumes:
  cardano-ipc: {}
  db-sync-data: {}
  postgres-data: {}

services:
  cardano-node:
    image: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CARDANO_IMAGE&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;ghcr&lt;/span&gt;&lt;span class="p"&gt;.io/intersectmbo/cardano-node&lt;/span&gt;:10.5.3&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    platform: linux/amd64
    restart: unless-stopped
    container_name: cardano-node
    ports:
      - "3001:3001"
    environment:
      - NETWORK=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CARDANO_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - CARDANO_NODE_SOCKET_PATH=/ipc/node.socket
    volumes:
      - cardano-ipc:/ipc
      - ./cardano-data:/data
    networks:
      - midnight-node-net

  postgres:
    image: postgres:15.3
    platform: linux/amd64
    restart: unless-stopped
    container_name: db-sync-postgres
    environment:
      - POSTGRES_PASSWORD=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - POSTGRES_DB=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - POSTGRES_USER=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "127.0.0.1:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; -d &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"]
      interval: 5s
      timeout: 5s
      retries: 20
    networks:
      - midnight-node-net

  cardano-db-sync:
    image: ghcr.io/intersectmbo/cardano-db-sync:13.6.0.5
    platform: linux/amd64
    restart: unless-stopped
    container_name: cardano-db-sync
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - NETWORK=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CARDANO_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - POSTGRES_HOST=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - POSTGRES_PORT=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - POSTGRES_DB=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - POSTGRES_USER=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      - POSTGRES_PASSWORD=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    volumes:
      - cardano-ipc:/node-ipc
      - db-sync-data:/var/lib
    networks:
      - midnight-node-net
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; postgres.env &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; midnight.env &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; cardano-db-sync.compose.yml &lt;span class="se"&gt;\&lt;/span&gt;
  up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check container status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; postgres.env &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; midnight.env &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; cardano-db-sync.compose.yml &lt;span class="se"&gt;\&lt;/span&gt;
  ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow logs while Cardano-db-sync catches up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; cardano-db-sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query Cardano-db-sync progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; db-sync-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-tAc&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT 100 * (
      EXTRACT(EPOCH FROM (MAX(time) AT TIME ZONE 'UTC')) -
      EXTRACT(EPOCH FROM (MIN(time) AT TIME ZONE 'UTC'))
    ) / (
      EXTRACT(EPOCH FROM (NOW() AT TIME ZONE 'UTC')) -
      EXTRACT(EPOCH FROM (MIN(time) AT TIME ZONE 'UTC'))
    ) AS sync_percent
    FROM block;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait until the value is close to &lt;code&gt;100&lt;/code&gt;. The exact number may fluctuate near the tip. The Midnight node can start before this completes, but it may report mainchain follower errors or stay near the first blocks until Cardano-db-sync is usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull and inspect the Midnight node image
&lt;/h2&gt;

&lt;p&gt;Pull the pinned 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 pull &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm that the image contains the chain resources for your environment:&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;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"/res/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see files such as &lt;code&gt;chain-spec-raw.json&lt;/code&gt;, &lt;code&gt;chain-spec.json&lt;/code&gt;, and &lt;code&gt;pc-chain-config.json&lt;/code&gt;. Stop if the directory is missing. A missing chain resource usually means the image version and network do not match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the Midnight full node
&lt;/h2&gt;

&lt;p&gt;Create the node volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create midnight-node-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the full node:&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;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; midnight-full-node &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; midnight-node-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 30333:30333 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:9944:9944 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:9615:9615 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; midnight-node-data:/node &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"CFG_PRESET=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"DB_SYNC_POSTGRES_CONNECTION_STRING=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_SYNC_POSTGRES_CONNECTION_STRING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/res/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/chain-spec-raw.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/node/chain &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/ip4/0.0.0.0/tcp/30333 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"midnight-full-node"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rpc-external&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rpc-methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Safe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prometheus-external&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bootnodes&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_BOOTNODE_1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bootnodes&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_BOOTNODE_2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-private-ip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This binds the RPC endpoint to &lt;code&gt;127.0.0.1:9944&lt;/code&gt; on the host, even though the node listens on all interfaces inside the container. Keep RPC private unless you put it behind explicit access control.&lt;/p&gt;

&lt;p&gt;Follow the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; midnight-full-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want to see peer discovery, block import, and continuing progress. Do not judge the node from the first minute. Initial startup includes database creation, chain spec loading, peer discovery, and mainchain follower checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run an archive node instead
&lt;/h2&gt;

&lt;p&gt;Use archive mode only when you need historical state. Start from a clean Midnight data volume when switching between full and archive mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; midnight-full-node &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;midnight-node-data &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;docker volume create midnight-node-data

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; midnight-archive-node &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; midnight-node-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 30333:30333 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:9944:9944 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:9615:9615 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; midnight-node-data:/node &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"CFG_PRESET=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"DB_SYNC_POSTGRES_CONNECTION_STRING=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_SYNC_POSTGRES_CONNECTION_STRING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/res/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/chain-spec-raw.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/node/chain &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/ip4/0.0.0.0/tcp/30333 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"midnight-archive-node"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rpc-external&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rpc-methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Safe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prometheus-external&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bootnodes&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_BOOTNODE_1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bootnodes&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_BOOTNODE_2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pruning&lt;/span&gt; archive &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-private-ip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitor sync and block height
&lt;/h2&gt;

&lt;p&gt;The health endpoint gives a quick process check:&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;-fsS&lt;/span&gt; http://127.0.0.1:9944/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use JSON-RPC for node status:&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;-fsS&lt;/span&gt; &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;'{"jsonrpc":"2.0","method":"system_health","params":[],"id":1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9944 &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example shape:&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;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&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;"peers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isSyncing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"shouldHavePeers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;Check the latest local 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;-fsS&lt;/span&gt; &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;'{"jsonrpc":"2.0","method":"chain_getBlock","params":[],"id":1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9944 &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.result.block.header.number'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The block number is hexadecimal. Convert it to decimal:&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="nv"&gt;LOCAL_HEIGHT_HEX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &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;'{"jsonrpc":"2.0","method":"chain_getBlock","params":[],"id":1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9944 &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.result.block.header.number'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%d\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;LOCAL_HEIGHT_HEX&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a reusable RPC helper:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rpc.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;http&lt;/span&gt;://127.0.0.1:9944&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
METHOD="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;system_health&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
PARAMS="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"

curl -fsS &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -H "Content-Type: application/json" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -d "{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;jsonrpc&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;2.0&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;method&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;METHOD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;params&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PARAMS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;:1}" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RPC_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x rpc.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./rpc.sh http://127.0.0.1:9944 system_health | jq &lt;span class="nb"&gt;.&lt;/span&gt;
./rpc.sh http://127.0.0.1:9944 rpc_methods | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.result.methods[]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a height monitor that compares your node with the public Preview RPC endpoint:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; monitor-height.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/usr/bin/env bash
set -euo pipefail

LOCAL_RPC="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOCAL_RPC&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;http&lt;/span&gt;://127.0.0.1:9944&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
REMOTE_RPC="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_RPC&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;https&lt;/span&gt;://rpc.preview.midnight.network&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
INTERVAL_SECONDS="&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INTERVAL_SECONDS&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;30&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"

rpc_block_number() {
  local url="&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="sh"&gt;"
  local hex_number
  hex_number=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &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;'{"jsonrpc":"2.0","method":"chain_getBlock","params":[],"id":1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.result.block.header.number // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;

  if [ -z "&lt;/span&gt;&lt;span class="nv"&gt;$hex_number&lt;/span&gt;&lt;span class="sh"&gt;" ]; then
    printf '0&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'
    return
  fi

  printf '%d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;' "&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;hex_number&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="sh"&gt;"
}

while true; do
  local_height=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;rpc_block_number &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_RPC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
  remote_height=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;rpc_block_number &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_RPC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
  timestamp=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y-%m-%dT%H:%M:%SZ"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;

  if [ "&lt;/span&gt;&lt;span class="nv"&gt;$local_height&lt;/span&gt;&lt;span class="sh"&gt;" -eq 0 ] || [ "&lt;/span&gt;&lt;span class="nv"&gt;$remote_height&lt;/span&gt;&lt;span class="sh"&gt;" -eq 0 ]; then
    printf '%s local=%s remote=%s lag=unknown&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;' &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
      "&lt;/span&gt;&lt;span class="nv"&gt;$timestamp&lt;/span&gt;&lt;span class="sh"&gt;" "&lt;/span&gt;&lt;span class="nv"&gt;$local_height&lt;/span&gt;&lt;span class="sh"&gt;" "&lt;/span&gt;&lt;span class="nv"&gt;$remote_height&lt;/span&gt;&lt;span class="sh"&gt;"
  else
    lag=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;remote_height &lt;span class="o"&gt;-&lt;/span&gt; local_height&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="sh"&gt;
    printf '%s local=%s remote=%s lag=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;' &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
      "&lt;/span&gt;&lt;span class="nv"&gt;$timestamp&lt;/span&gt;&lt;span class="sh"&gt;" "&lt;/span&gt;&lt;span class="nv"&gt;$local_height&lt;/span&gt;&lt;span class="sh"&gt;" "&lt;/span&gt;&lt;span class="nv"&gt;$remote_height&lt;/span&gt;&lt;span class="sh"&gt;" "&lt;/span&gt;&lt;span class="nv"&gt;$lag&lt;/span&gt;&lt;span class="sh"&gt;"
  fi

  sleep "&lt;/span&gt;&lt;span class="nv"&gt;$INTERVAL_SECONDS&lt;/span&gt;&lt;span class="sh"&gt;"
done
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x monitor-height.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&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="nv"&gt;LOCAL_RPC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:9944 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;REMOTE_RPC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://rpc.preview.midnight.network &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;INTERVAL_SECONDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
./monitor-height.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A healthy node shows increasing local height and a shrinking lag. A synced node stays close to the remote height and keeps peers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitor peer connectivity
&lt;/h2&gt;

&lt;p&gt;Start with the RPC view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./rpc.sh http://127.0.0.1:9944 system_health | jq &lt;span class="s1"&gt;'.result'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interpret the fields like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Healthy value&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;peers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Greater than &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The node has live P2P connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isSyncing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt; during catch-up, then &lt;code&gt;false&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The node still imports historical blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shouldHavePeers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Usually &lt;code&gt;true&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The node expects to participate in P2P&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check that the P2P port listens on the host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-ltnp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;':30333'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allow inbound P2P traffic if you use &lt;code&gt;ufw&lt;/code&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="nb"&gt;sudo &lt;/span&gt;ufw allow 30333/tcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check outbound reachability to a boot node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nc &lt;span class="nt"&gt;-vz&lt;/span&gt; bootnode-1.preview.midnight.network 30333
nc &lt;span class="nt"&gt;-vz&lt;/span&gt; bootnode-2.preview.midnight.network 30333
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check recent peer-related logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 15m midnight-full-node &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'peer|bootnode|disconnect|dial|sync|import'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Prometheus metrics if enabled:&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;-fsS&lt;/span&gt; http://127.0.0.1:9615/metrics &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'peer|height|sync|block'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshoot a node stuck on block 1
&lt;/h2&gt;

&lt;p&gt;A node stuck on block &lt;code&gt;1&lt;/code&gt; usually has a configuration, dependency, or P2P problem. Work through the checks in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Confirm the image and network match
&lt;/h3&gt;

&lt;p&gt;Print the active values:&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;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; ./midnight.env
&lt;span class="nb"&gt;.&lt;/span&gt; ./postgres.env
&lt;span class="nb"&gt;set&lt;/span&gt; +a

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'network=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MIDNIGHT_NETWORK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'image=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'cardano_network=%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CARDANO_NETWORK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Preview, use &lt;code&gt;MIDNIGHT_NETWORK=preview&lt;/code&gt;, &lt;code&gt;CARDANO_NETWORK=preview&lt;/code&gt;, and a Preview-compatible image. For Preprod, all three values must point to Preprod. Do not run a Preview image against Preprod boot nodes or a Preprod Cardano-db-sync database.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Confirm the chain spec exists inside the image
&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;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"/res/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NETWORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/chain-spec-raw.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No output means the file exists. Any nonzero exit means the image or network is wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Confirm Cardano-db-sync is caught up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 cardano-db-sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then rerun the sync percentage query from the setup section. If Cardano-db-sync is far behind, let it finish. The Midnight Docker repository documents a related error that appears when the Cardano node or db-sync is not ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unable to author block in slot. Failure creating inherent data provider:
'No latest block on chain.' not found.
Possible causes: main chain follower configuration error, db-sync not synced fully,
or data not set on the main chain.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a full node, the practical response is the same: check the Cardano services, database connection string, and network selection before resetting the Midnight data volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Check the PostgreSQL connection string from inside the node network
&lt;/h3&gt;

&lt;p&gt;Run a temporary PostgreSQL client on the same Docker network:&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;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; midnight-node-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  postgres:15.3 &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-h&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'SELECT COUNT(*) FROM block;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this fails, fix PostgreSQL before you touch the Midnight node.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Check peers and boot nodes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./rpc.sh http://127.0.0.1:9944 system_health | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;peers&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, check firewall rules, cloud security groups, outbound access, and boot node values. If peers appear and disappear repeatedly, check system time, network stability, and image or chain spec mismatch.&lt;/p&gt;

&lt;p&gt;Check clock sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timedatectl status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable NTP if needed:&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;timedatectl set-ntp &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Check for memory pressure or OOM kills
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect midnight-full-node &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} RestartCount={{.RestartCount}}'&lt;/span&gt;

dmesg &lt;span class="nt"&gt;-T&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'out of memory|oom|killed process'&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 20 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the node or PostgreSQL is OOM-killed, add RAM before restarting. Repeated OOM events can corrupt local state or keep the node in a restart loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Reset only the data that needs resetting
&lt;/h3&gt;

&lt;p&gt;Reset the Midnight node data if you changed the Midnight network, changed from full to archive mode, used the wrong chain spec, or hit a corrupted Midnight database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; midnight-full-node &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;midnight-node-data &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;docker volume create midnight-node-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not delete the PostgreSQL or Cardano-db-sync volumes unless you changed the Cardano network, damaged the database, or intentionally want a full resync.&lt;/p&gt;

&lt;p&gt;If you must reset Cardano-db-sync too, stop services and identify the exact volume names before removal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; postgres.env &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; midnight.env &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; cardano-db-sync.compose.yml &lt;span class="se"&gt;\&lt;/span&gt;
  down

docker volume &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Name}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'cardano|db-sync|postgres'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove only the volumes that belong to this node directory and this Compose project. Your Compose project name may differ by host, so do not paste a volume deletion command from another machine into production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify that the node is synced and healthy
&lt;/h2&gt;

&lt;p&gt;Use this checklist after initial sync and after every restart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Containers are running.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'table {{.Names}}\t{{.Status}}\t{{.Ports}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The health endpoint responds.
&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;-fsS&lt;/span&gt; http://127.0.0.1:9944/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The node has peers.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./rpc.sh http://127.0.0.1:9944 system_health | jq &lt;span class="s1"&gt;'.result.peers'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The block height increases.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;_ &lt;span class="k"&gt;in &lt;/span&gt;1 2 3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  ./rpc.sh http://127.0.0.1:9944 chain_getBlock &lt;span class="se"&gt;\&lt;/span&gt;
    | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.result.block.header.number'&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;12
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Midnight's documented block time is &lt;code&gt;6&lt;/code&gt; seconds, so a healthy node near the tip should observe new blocks over short intervals. Network conditions can vary, so use repeated checks instead of a single sample.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The local height is near the public RPC height for the same environment.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LOCAL_RPC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:9944 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;REMOTE_RPC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://rpc.preview.midnight.network &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;INTERVAL_SECONDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;timeout &lt;/span&gt;35 ./monitor-height.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Prometheus responds.
&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;-fsS&lt;/span&gt; http://127.0.0.1:9615/metrics &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/midnight-metrics.txt
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /tmp/midnight-metrics.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Logs show normal operation, not repeated disconnects, database errors, or panics.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 30m midnight-full-node &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'error|warn|panic|disconnect|database|db-sync'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A healthy node has a running container, a responsive local RPC endpoint, nonzero peers, increasing block height, low height lag, and no repeated fatal log lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintain the node
&lt;/h2&gt;

&lt;p&gt;Check the compatibility matrix before every upgrade. Stop the node, pull the new image, and restart with the same network and data volume only when the release notes allow that upgrade path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; midnight-full-node

docker pull &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Re-run the docker run command from the setup section with the new pinned image.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back up configuration files, not just volumes:&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;tar&lt;/span&gt; &lt;span class="nt"&gt;-czf&lt;/span&gt; &lt;span class="s2"&gt;"midnight-node-config-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y%m%dT%H%M%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  midnight.env &lt;span class="se"&gt;\&lt;/span&gt;
  postgres.env &lt;span class="se"&gt;\&lt;/span&gt;
  cardano-db-sync.compose.yml &lt;span class="se"&gt;\&lt;/span&gt;
  rpc.sh &lt;span class="se"&gt;\&lt;/span&gt;
  monitor-height.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep these files out of public repositories. &lt;code&gt;postgres.env&lt;/code&gt; contains credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;After the node is stable, connect local tooling to &lt;code&gt;http://127.0.0.1:9944&lt;/code&gt;, import the Midnight Insomnia collection if you prefer a GUI client, and add external monitoring for container restarts, peer count, height lag, disk use, and PostgreSQL health.&lt;/p&gt;

&lt;p&gt;For DApp development, pair the node with the Midnight indexer, wallet tooling, and a proof server. For production operation, keep RPC private, monitor disk growth, and rehearse upgrades on Preview or Preprod before touching Mainnet.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/nodes" rel="noopener noreferrer"&gt;Midnight node overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/nodes/full-node" rel="noopener noreferrer"&gt;Setting up full and archive nodes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/nodes/cardano-db-sync" rel="noopener noreferrer"&gt;Setting up Cardano-db-sync&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/nodes/node-endpoints" rel="noopener noreferrer"&gt;Node endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/concepts/network-architecture/rpc-networking" rel="noopener noreferrer"&gt;RPC interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/relnotes/support-matrix" rel="noopener noreferrer"&gt;Compatibility matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/relnotes/network" rel="noopener noreferrer"&gt;Environments and endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/midnightntwrk/midnight-node/tags" rel="noopener noreferrer"&gt;Midnight node Docker image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-node-docker" rel="noopener noreferrer"&gt;midnight-node-docker repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>midnightfordevs</category>
    </item>
    <item>
      <title>Affiliates on Autopilot with CREEM's AffiliateHub</title>
      <dc:creator>Arihant Agarwal</dc:creator>
      <pubDate>Tue, 31 Mar 2026 23:34:13 +0000</pubDate>
      <link>https://dev.to/arihant_agarwal_359eb9f57/affiliates-on-autopilot-with-creems-affiliatehub-4k47</link>
      <guid>https://dev.to/arihant_agarwal_359eb9f57/affiliates-on-autopilot-with-creems-affiliatehub-4k47</guid>
      <description>&lt;p&gt;Most affiliate setups require you to bolt a third-party tool onto your payment processor, wrangle a webhook integration, and maintain two separate billing systems every time you update your pricing. Creem eliminates all of that. Because Creem is your Merchant of Record — meaning it already processes every sale, handles tax compliance, and records every transaction — adding an affiliate program is a configuration step, not an engineering project.&lt;/p&gt;

&lt;p&gt;The result is attribution that never breaks (because the tracking is inside the same system that records the sale), payouts that actually move money to partners internationally, and a dashboard that shows you exactly which affiliates are driving revenue. This guide walks through the entire setup from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Creem's Affiliate Platform Is Structured
&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%2F4leamfqs04eokd1l93mt.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%2F4leamfqs04eokd1l93mt.png" alt=" " width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your affiliates never touch your Creem dashboard. They get a purpose-built portal under a separate subdomain, with their own login. This matters because it means you never have to manage permissions, roles, or the awkward moment where a partner accidentally sees your revenue numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Create Your Affiliate Program
&lt;/h2&gt;

&lt;p&gt;In your Creem dashboard, navigate to Growth → Affiliate Hub in the left sidebar. If this is your first program, you'll land on the creation wizard. If you've been here before, click New Program.&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%2Fby5y9la53mbo2wa3o2fz.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%2Fby5y9la53mbo2wa3o2fz.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in the four fields that define your program:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 Program Name.&lt;/strong&gt; What your affiliates will see when they join. Use your product name — something they'll recognise, not an internal codename.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 Website URL.&lt;/strong&gt; Where referral links send visitors. This should be your public-facing landing page or homepage — not your app login, not your dashboard. This is the most common setup mistake and it kills your conversion rate before it has a chance to breathe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 Program Slug.&lt;/strong&gt; The URL identifier appended to every affiliate link. If your slug is my-saas, referral links read as yoursite.com?ref=my-saas-[partnercode]. Keep it lowercase, hyphen-separated, no special characters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 Commission Rate.&lt;/strong&gt; The percentage of each sale paid to the affiliate. For SaaS products, the range that meaningfully motivates promotion is typically 20–40%. Below 10% is rarely worth an affiliate's effort. Above 50% works only if your margins support it.&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%2Foptvf9idgilfucsfwclo.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%2Foptvf9idgilfucsfwclo.png" alt=" " width="800" height="811"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you save, your Affiliate Hub dashboard appears. The program is live, the commission structure is set, and Creem is ready to track referrals. Nothing to integrate, nothing to configure externally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Invite Your First Affiliates
&lt;/h2&gt;

&lt;p&gt;From the Affiliate Hub, click Invite Partner. A modal appears asking for two things: your partner's name and email address. Enter both and click Send Invite.&lt;/p&gt;

&lt;p&gt;Creem sends your partner an email with a join link. In your Partners list, they immediately appear with an Invited status badge. Once they accept, their badge flips to Active and they appear in your analytics.&lt;/p&gt;

&lt;p&gt;By default, programs are invite-only. Anyone you haven't explicitly invited cannot join. If you want an open program where potential partners can self-enroll, toggle Public Program in your program settings. For most SaaS founders, invite-only is the right default — it keeps your affiliate roster deliberate and manageable.&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%2F6pt5pw7mdfx8tugkpo4i.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%2F6pt5pw7mdfx8tugkpo4i.png" alt=" " width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Configure Per-Affiliate Settings
&lt;/h2&gt;

&lt;p&gt;Click any affiliate in your Partners list to open their detail panel. This is where Creem's affiliate management goes from simple to genuinely powerful.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Custom commission rates&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Every affiliate inherits your program's default commission rate — but you can override it per-partner. A newsletter writer with 50,000 subscribers who drives consistent volume deserves a different rate than a new partner you're still evaluating. You can set individual rates anywhere from 0 to 95%, independent of the program default, without affecting anyone else.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Product restrictions&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
If you sell multiple products, you can restrict which ones a given affiliate is authorised to promote. Leave this blank and they can link to anything. Specify products and only those links earn commission. This is essential if you have a flagship product you want to protect from off-brand promotion, or a legacy product you'd prefer not to amplify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — What Your Affiliates See
&lt;/h2&gt;

&lt;p&gt;When a partner accepts their invitation, they land at affiliates.creem.io and sign in with Google or a magic link — using the same email you invited. First-time users complete a brief profile: display name, bio, website, and optional social links. This is what you see when reviewing partners, so encourage them to fill it out properly. A blank profile is a lost first impression.&lt;/p&gt;

&lt;p&gt;Their dashboard surface three things immediately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 Referral link.&lt;/strong&gt; Their unique URL, one-click to copy, displayed at the top of the dashboard. This is what they share everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 Performance metrics.&lt;/strong&gt; Revenue, customers referred, total clicks, and conversion rate — all updating in real time as sales happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 30-day earnings chart.&lt;/strong&gt; A visual breakdown of commission earned each day. This is the chart that turns passive affiliates into active ones — seeing a spike after a promotion is motivating in a way that a static number is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How tracking works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a visitor clicks an affiliate's referral link, Creem sets a tracking cookie in their browser. Any purchase completed within the cookie duration window is automatically attributed to that affiliate and logged against their commission balance. No manual step is required on either side.&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%2F18779dy20u0jgqhcz80o.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%2F18779dy20u0jgqhcz80o.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Creating affiliate-specific discount codes&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
In your Creem dashboard, navigate to Products → Discount Codes and create a new code. Name it after your affiliate — their handle or brand makes it memorable and self-attributing. Set your discount amount, activate the code, and share it alongside their referral link. You now have two independent signals tracking the same customer. Belt and suspenders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Track Performance in the Affiliate Hub
&lt;/h2&gt;

&lt;p&gt;Your Affiliate Hub is the control room. At the top, six headline metrics give you an instant program health check:&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%2F66qufindj6ib03ocwd0e.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%2F66qufindj6ib03ocwd0e.png" alt=" " width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Understand the Payout Flow
&lt;/h2&gt;

&lt;p&gt;Affiliates request payouts from the &lt;strong&gt;Balance&lt;/strong&gt; page inside their portal. Three balance states are shown:&lt;/p&gt;

&lt;p&gt;Before an affiliate can request their first payout, two things must happen. First, they complete identity verification (KYC) through Sumsub — a government-issued ID and selfie, typically approved within 24 hours. Second, they add a payout method: bank transfer via Paysway, or USDC cryptocurrency via Mural. International affiliates particularly appreciate the crypto option, which eliminates the wire transfer friction that kills payouts to non-US partners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro Founder Tip:&lt;/strong&gt; In your program settings, you can set a minimum payout threshold — the balance an affiliate must reach before they can request a withdrawal. This reduces payment processing overhead and simplifies your month-end accounting. A $50 or $100 minimum is common and rarely causes friction with active affiliates who are earning regularly.&lt;/p&gt;

&lt;h2&gt;
  
  
  You're Live
&lt;/h2&gt;

&lt;p&gt;At this point you have a fully operational affiliate program: a commission structure set, partners invited, referral links generated, discount codes paired as backup attribution, and a payout flow that handles identity verification and international transfers automatically.&lt;/p&gt;

&lt;p&gt;The last question is where to find affiliates worth inviting. The fastest answer is your existing user base — specifically anyone who has already mentioned your product publicly in a review, tweet, or blog post. They believe in the product and have an audience. You're adding a financial incentive to something they're already doing.&lt;/p&gt;

&lt;p&gt;Beyond users: complementary tool founders (different product, same audience) make excellent affiliate partners because the cross-promotion is genuinely useful to both sides. One well-placed partnership outperforms fifty cold outreach attempts every time.&lt;/p&gt;

&lt;p&gt;When you do invite someone, don't send them just the referral link. Send them a short explainer of what converts, a sample caption or tweet they can remix, and your best-performing screenshot or demo. The easier you make it to promote you, the more they actually will. The link alone sits in a folder. A toolkit gets used.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
