<?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: Tminus1sec base.eth</title>
    <description>The latest articles on DEV Community by Tminus1sec base.eth (@tminus1s).</description>
    <link>https://dev.to/tminus1s</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3996717%2Ffb0baa9b-4af0-409d-8246-af52e4597c0e.jpg</url>
      <title>DEV Community: Tminus1sec base.eth</title>
      <link>https://dev.to/tminus1s</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tminus1s"/>
    <language>en</language>
    <item>
      <title>Building a .night domain profile viewer on Midnight with the Midnames SD</title>
      <dc:creator>Tminus1sec base.eth</dc:creator>
      <pubDate>Fri, 24 Jul 2026 01:59:21 +0000</pubDate>
      <link>https://dev.to/tminus1s/building-a-night-domain-profile-viewer-on-midnight-with-the-midnames-sd-4kph</link>
      <guid>https://dev.to/tminus1s/building-a-night-domain-profile-viewer-on-midnight-with-the-midnames-sd-4kph</guid>
      <description>&lt;p&gt;I'm a self-taught builder, and this week I built a small thing that made a concept finally click for me: a .night domain profile viewer. You type a name like tomin.night, it resolves on-chain, and it shows you the profile. This is the walkthrough — what a .night name actually is, the one SDK call that resolves it, the two gotchas that cost me time, and how I wrapped it into a tiny dApp.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/tomiin/midnames-profile-viewer" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnames-profile-viewer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What a .night name actually is&lt;/p&gt;

&lt;p&gt;On Midnight, .night domains are managed by Midnames. The important part for a developer: the name registry is a Compact smart contract deployed on-chain. The @midnames/sdk package literally ships the compiled contract (it exports Contract, ledger, witnesses, and a MANAGED_DIR of Compact artifacts).&lt;/p&gt;

&lt;p&gt;So "look up a domain" doesn't mean "call a company's REST API." It means read contract state. The domain, its owner, where it points, and its profile fields all live on the chain. That's the whole appeal: a .night name is a portable, on-chain identity you own.&lt;/p&gt;

&lt;p&gt;The one call that does the work&lt;/p&gt;

&lt;p&gt;Install the SDK (Node ≥ 22, ESM):&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
npm install @midnames/sdk&lt;br&gt;
npm pkg set type=module&lt;/p&gt;

&lt;p&gt;Resolving a domain is two lines:&lt;/p&gt;

&lt;p&gt;ts&lt;br&gt;
import { createDefaultProvider, getDomainProfile } from "@midnames/sdk";&lt;/p&gt;

&lt;p&gt;const provider = createDefaultProvider({ networkId: "mainnet" });&lt;br&gt;
const result = await getDomainProfile("tomin.night", { provider });&lt;/p&gt;

&lt;p&gt;if (result.success) {&lt;br&gt;
  console.dir(result.data, { depth: null });&lt;br&gt;
} else {&lt;br&gt;
  console.error(result.error); // e.g. DomainNotFoundError&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The SDK returns a result object with a success flag — no try/catch gymnastics for the "domain doesn't exist" case, which is nice.&lt;/p&gt;

&lt;p&gt;What comes back&lt;/p&gt;

&lt;p&gt;Here's the real shape of result.data for my domain (trimmed):&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
{&lt;br&gt;
  fullDomain: 'tomin.night',&lt;br&gt;
  info: {&lt;br&gt;
    id: 142n,&lt;br&gt;
    owner: '0e3240f66410...a9f36a6694',&lt;br&gt;
    ownerAddress: 'mn_addr1vglt...w63s8cp7sd',&lt;br&gt;
    target: {&lt;br&gt;
      type: 'shielded',&lt;br&gt;
      address: 'mn_shield-cpk1...nvxdys3gwdtn'&lt;br&gt;
    },&lt;br&gt;
    targetLocked: false&lt;br&gt;
  },&lt;br&gt;
  fields: Map(4) {&lt;br&gt;
    'epk' =&amp;gt; 'mn_shield-epk1...',&lt;br&gt;
    'profile_type' =&amp;gt; 'personal',&lt;br&gt;
    'discord' =&amp;gt; 'Uetto',&lt;br&gt;
    'Zealy' =&amp;gt; 'tminus1sec'&lt;br&gt;
  },&lt;br&gt;
  settings: { coinColor: Uint8Array(32) [...], costs: {...}, buyEnabled: false }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Two things jump out:&lt;/p&gt;

&lt;p&gt;target — the address the name points to, like a DNS record but for wallets. Mine resolves to a shielded address. So a wallet could let you "pay tomin.night" and quietly turn it into that address, instead of anyone copying 60 hex characters.&lt;br&gt;
fields — arbitrary key/value profile data (discord, socials, a profile_type, whatever you set). This is the identity layer.&lt;br&gt;
Gotcha #1: which network is your domain on?&lt;/p&gt;

&lt;p&gt;This one cost me real time. The Midnames SDK supports two networks, mainnet and preprod. I first ran a script against preprod and got:&lt;/p&gt;

&lt;p&gt;DomainNotFoundError: Domain not found: tomin.night&lt;/p&gt;

&lt;p&gt;The domain wasn't missing — it was registered on mainnet, and I was querying preprod. The tell is in the addresses: a mainnet owner address looks like mn_addr1..., while preprod is mn_addr_preprod1.... So point the provider at the network your domain actually lives on:&lt;/p&gt;

&lt;p&gt;ts&lt;br&gt;
const provider = createDefaultProvider({ networkId: "mainnet" }); // not "preprod"&lt;br&gt;
Gotcha #2: the SDK returns real JS types, not JSON&lt;/p&gt;

&lt;p&gt;Look again at that result: id is a BigInt (142n), coinColor is a Uint8Array, and fields is a Map. If you JSON.stringify that to send it to a browser, it throws on the BigInt and mangles the Map. So I wrote a small serializer:&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
function serialize(v) {&lt;br&gt;
  if (typeof v === 'bigint') return v.toString();&lt;br&gt;
  if (v instanceof Uint8Array) return Buffer.from(v).toString('hex');&lt;br&gt;
  if (v instanceof Map) return Object.fromEntries([...v].map(([k, val]) =&amp;gt; [k, serialize(val)]));&lt;br&gt;
  if (Array.isArray(v)) return v.map(serialize);&lt;br&gt;
  if (v &amp;amp;&amp;amp; typeof v === 'object') {&lt;br&gt;
    const o = {};&lt;br&gt;
    for (const [k, val] of Object.entries(v)) o[k] = serialize(val);&lt;br&gt;
    return o;&lt;br&gt;
  }&lt;br&gt;
  return v;&lt;br&gt;
}&lt;br&gt;
Wrapping it in a dApp&lt;/p&gt;

&lt;p&gt;I kept the architecture boring on purpose: the SDK runs in a tiny Node/Express backend (where it's happiest — no browser polyfills), and a plain HTML/JS page calls it and renders the card.&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
import express from 'express';&lt;br&gt;
import { createDefaultProvider, getDomainProfile } from '@midnames/sdk';&lt;/p&gt;

&lt;p&gt;const app = express();&lt;br&gt;
app.use(express.static('public'));&lt;/p&gt;

&lt;p&gt;const providers = {};&lt;br&gt;
const providerFor = (net) =&amp;gt; (providers[net] ??= createDefaultProvider({ networkId: net }));&lt;/p&gt;

&lt;p&gt;app.get('/api/resolve', async (req, res) =&amp;gt; {&lt;br&gt;
  const domain = String(req.query.domain || '').toLowerCase();&lt;br&gt;
  const network = String(req.query.network || 'mainnet').toLowerCase();&lt;br&gt;
  const result = await getDomainProfile(domain, { provider: providerFor(network) });&lt;br&gt;
  if (!result.success) return res.status(404).json({ error: result.error?.message });&lt;br&gt;
  res.json({ domain, network, data: serialize(result.data) });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;app.listen(5173, () =&amp;gt; console.log('&lt;a href="http://localhost:5173')" rel="noopener noreferrer"&gt;http://localhost:5173')&lt;/a&gt;);&lt;/p&gt;

&lt;p&gt;The front end is just an input, a network toggle, and a fetch to /api/resolve that renders the owner, the resolved target, and the profile fields. No wallet connect, no login — resolving a name is a public read.&lt;/p&gt;

&lt;p&gt;Run it&lt;br&gt;
bash&lt;br&gt;
git clone &lt;a href="https://github.com/tomiin/midnames-profile-viewer" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnames-profile-viewer&lt;/a&gt;&lt;br&gt;
cd midnames-profile-viewer&lt;br&gt;
npm install&lt;br&gt;
npm start&lt;/p&gt;

&lt;h1&gt;
  
  
  open &lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Takeaways&lt;br&gt;
A .night name is an entry in an on-chain Compact contract you own — resolvable to an address plus a bag of profile fields. It's a genuinely nice identity primitive.&lt;br&gt;
Reading it is one SDK call; the friction is all around it: pick the right network, and serialize the rich JS types (BigInt / Uint8Array / Map) before they hit a browser.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/tomiin/midnames-profile-viewer" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnames-profile-viewer&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  MidnightForDevs
&lt;/h1&gt;

</description>
      <category>midnightfordevs</category>
      <category>blockchain</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Midnight sprint: a sealed-bid auction, a stubborn error 170, and everything it taught me"</title>
      <dc:creator>Tminus1sec base.eth</dc:creator>
      <pubDate>Sun, 19 Jul 2026 01:46:51 +0000</pubDate>
      <link>https://dev.to/tminus1s/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-taught-me-24kd</link>
      <guid>https://dev.to/tminus1s/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-taught-me-24kd</guid>
      <description>&lt;p&gt;I'm a self-taught builder, not a career dev. I lean on AI to help me a lot when my syntax breaks and explanations are rather verbose, I read a lot of&lt;br&gt;
docs, and I break things constantly. So this is a build story from that seat — the&lt;br&gt;
Midnight Expert sprint, everything I shipped, and every wall I hit getting there.&lt;br&gt;
If you're also learning this stuff, the messy parts below are the useful parts.&lt;/p&gt;

&lt;p&gt;The whole sprint ran through the &lt;strong&gt;Midnight Expert&lt;/strong&gt; plugins inside &lt;strong&gt;Claude Code&lt;/strong&gt;&lt;br&gt;
— a marketplace of AI agent plugins for the Midnight blockchain (Compact contracts,&lt;br&gt;
DApp frontends, toolchain, an error-code lookup, and more). I installed it there,&lt;br&gt;
ran the diagnostics, and did the contract + tooling work from that setup. The&lt;br&gt;
finishing and debugging happened in a mix of Claude Code and Claude's desktop&lt;br&gt;
agent, but the Midnight-specific muscle came from those plugins.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I shipped
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Compact smart contract&lt;/strong&gt; — a sealed-bid auction with a real nullifier:
&lt;a href="https://github.com/tomiin/midnight-sealed-bid-commit-reveal" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnight-sealed-bid-commit-reveal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployed it to Midnight Preprod&lt;/strong&gt; with a live on-chain interaction (contract
address is in that repo's README).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A PR back to the Midnight Expert repo&lt;/strong&gt; improving an error-code entry I'd just
spent hours living inside.&lt;/li&gt;
&lt;li&gt;(Bonus, same journey: a from-scratch &lt;strong&gt;CLI wallet&lt;/strong&gt; —
&lt;a href="https://github.com/tomiin/midnight-cli-wallet" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnight-cli-wallet&lt;/a&gt; — which is where I accidentally
learned the fix that unblocked everything else.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Getting set up (the easy 10-XP wins)
&lt;/h2&gt;

&lt;p&gt;First three quests were the warm-up: explore the marketplace, star the repo, and&lt;br&gt;
install + run diagnostics. In Claude Code that last one is a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/midnight-expert:doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It checks your compiler version, tooling, and MCP servers and tells you what's&lt;br&gt;
green. Mine flagged that my Compact compiler had upgraded (0.30 → 0.31.1) and a&lt;br&gt;
couple of harmless warnings. Good baseline before touching anything real.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quest 1 — Writing a Compact contract
&lt;/h2&gt;

&lt;p&gt;The idea: a &lt;strong&gt;sealed-bid auction&lt;/strong&gt;. While bidding is open, nobody — not other&lt;br&gt;
bidders, not even the auctioneer — can see what anyone bid. When bidding closes,&lt;br&gt;
people reveal their numbers, the contract checks each against what was locked in&lt;br&gt;
earlier, and the highest honest bid wins. Think sealed envelopes: shut while you're&lt;br&gt;
bidding, opened at reveal time.&lt;/p&gt;

&lt;p&gt;The part I actually wanted to get right was the &lt;strong&gt;nullifier&lt;/strong&gt;. Compact has no&lt;br&gt;
&lt;code&gt;msg.sender&lt;/code&gt; — there's no built-in "who's calling." So a caller proves who they are&lt;br&gt;
by knowing a secret key that never leaves their machine, and the contract derives a&lt;br&gt;
one-way fingerprint (a nullifier) from it. Two details make it real: it's&lt;br&gt;
&lt;strong&gt;domain-separated&lt;/strong&gt; (tagged &lt;code&gt;"sbid:v1:nullifier"&lt;/code&gt; so it can't be confused with any&lt;br&gt;
other hash from the same key), and it's the &lt;strong&gt;double-bid guard&lt;/strong&gt; (your commitment is&lt;br&gt;
filed under your nullifier, so a second bid collides and bounces). One identity, one&lt;br&gt;
bid, and your identity never hits the chain.&lt;/p&gt;

&lt;p&gt;I wrote it with the &lt;code&gt;compact-core&lt;/code&gt; plugin, compiled it, and got a passing test suite&lt;br&gt;
before going anywhere near a network. The honest boundary, which a lot of "private"&lt;br&gt;
demos skip: the bid &lt;em&gt;amounts&lt;/em&gt; are private while bidding is open, but the&lt;br&gt;
nullifiers and commitments are public — that's what makes the anti-double-bid check&lt;br&gt;
work.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quest 2 — Ship and deploy it (a.k.a. the error 170 saga)
&lt;/h2&gt;

&lt;p&gt;This is where I lost a night, so buckle up. The quest wants the contract deployed to&lt;br&gt;
&lt;strong&gt;Preprod&lt;/strong&gt; (a public test network) with at least one real on-chain interaction.&lt;/p&gt;

&lt;p&gt;My deploy CLI would build the transaction, prove it, submit it — and the node would&lt;br&gt;
spit back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1010: Invalid Transaction: Custom error: 170
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every single time. Error 170 is &lt;code&gt;InvalidDustSpendProof&lt;/code&gt;. Here's the thing I didn't&lt;br&gt;
understand at first: on Midnight you don't pay fees with the main token (NIGHT). You&lt;br&gt;
register NIGHT to &lt;em&gt;generate&lt;/em&gt; a fee resource called &lt;strong&gt;DUST&lt;/strong&gt;, and every transaction&lt;br&gt;
proves a little DUST spend to cover its fee. &lt;strong&gt;170 is the fee leg getting rejected&lt;/strong&gt;&lt;br&gt;
— nothing to do with my contract.&lt;/p&gt;

&lt;p&gt;I assumed it was the test network being slow (a Midnight dev even confirmed that&lt;br&gt;
version of it happens when the public indexer lags behind the node). So I tried the&lt;br&gt;
other public network, Preview. Its faucet's human-check spun forever and then locked&lt;br&gt;
me out for 24 hours. Two public networks, two different dead ends, same night.&lt;/p&gt;

&lt;p&gt;So I did the thing that actually cracked it: I stood up a &lt;strong&gt;local devnet&lt;/strong&gt; with the&lt;br&gt;
&lt;code&gt;midnight-tooling&lt;/code&gt; plugin — a node and indexer in Docker, right on my machine, with&lt;br&gt;
zero lag between them. And it &lt;em&gt;still&lt;/em&gt; threw 170. That was the lightbulb. If a chain&lt;br&gt;
that's perfectly in sync with itself rejects my transaction too, the problem isn't&lt;br&gt;
the network — it's my code.&lt;/p&gt;

&lt;p&gt;Three things had to be fixed, in order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Rebuild the transaction on every retry — don't resubmit the same one.&lt;/strong&gt; My retry&lt;br&gt;
loop was resubmitting the &lt;em&gt;exact same&lt;/em&gt; proven transaction each time. But a 170's&lt;br&gt;
stale DUST proof is baked into that transaction, so re-sending it just re-presents&lt;br&gt;
the same dead proof. On a chain minting blocks every few seconds, it's stale the&lt;br&gt;
instant it's built. The fix was to rebuild and re-balance the whole thing on each&lt;br&gt;
attempt so every try carries a fresh proof:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Attempt 1: building (fresh dust balance), proving, submitting…
  attempt 1 failed: Transaction submission error   (that's the 170)
Attempt 2: building (fresh dust balance), proving, submitting…
Transfer submitted. Tx: 0087338e7833176e...c008fe3d
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attempt two, fresh rebuild, straight through. (I found this on the wallet first, then&lt;br&gt;
carried it to the deploy.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Keep the wallet's DUST synced to the current tip.&lt;/strong&gt; Even with the rebuild loop,&lt;br&gt;
if the wallet's DUST state is hours behind the chain it can't even balance the fee —&lt;br&gt;
you get instant "could not balance dust" failures instead of 170. My deploy wallet&lt;br&gt;
was restoring from a checkpoint saved earlier in the day. I re-synced it to the&lt;br&gt;
current tip first, then deployed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use a strong local password.&lt;/strong&gt; Once the DUST was sorted, the deploy got all the&lt;br&gt;
way to storing private state and died with &lt;code&gt;PasswordValidationError: Password must&lt;br&gt;
contain at least 3 of: uppercase, lowercase, digits, special. Found: 2&lt;/code&gt;. The SDK now&lt;br&gt;
enforces that on the local private-state store. Bumped my dev password to four&lt;br&gt;
classes and moved on.&lt;/p&gt;

&lt;p&gt;And then, finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;✅ DEPLOYED. Contract address: ad08e233a172874748b05ab40a30c9217699650115aa5650c3c671accfee4244
Placing one sealed bid (on-chain interaction)…
✅ placeSealedBid submitted.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live on Preprod, with a real interaction. After all that, it went through on the&lt;br&gt;
second attempt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quest 3 — Extend Midnight Expert
&lt;/h2&gt;

&lt;p&gt;The Extend quest wants a real PR back to the &lt;a href="https://github.com/devrelaicom/midnight-expert" rel="noopener noreferrer"&gt;midnight-expert&lt;br&gt;
repo&lt;/a&gt;. I had the perfect thing,&lt;br&gt;
because I'd just been &lt;em&gt;inside&lt;/em&gt; the exact gap.&lt;/p&gt;

&lt;p&gt;The repo has a &lt;code&gt;midnight-status-codes&lt;/code&gt; plugin — a searchable catalog of every&lt;br&gt;
Midnight error code. I looked up 170, and its only suggested fix was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Regenerate the dust spend proof using the proof server"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is exactly the trap I'd fallen into. Re-proving or resubmitting the &lt;em&gt;same&lt;/em&gt;&lt;br&gt;
transaction can never clear a 170 — that's the whole lesson I'd just paid for in&lt;br&gt;
hours. So I expanded the entry with the remediation that actually works: sync the&lt;br&gt;
DUST to the tip, rebuild-don't-resubmit on 170, and the practical tell (instant&lt;br&gt;
"could not balance dust" = too stale to balance, resync; a 170 &lt;em&gt;after&lt;/em&gt; proving = the&lt;br&gt;
block-advance race, which rebuilding rides through).&lt;/p&gt;

&lt;p&gt;Their repo has a schema check and a test suite, and both pass with the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schema check PASSED
Results: 14 passed, 0 failed
All checks passed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small, genuine, and something I could only have written by living through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell past-me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A retry only helps if the next attempt is different from the one that failed.&lt;/strong&gt;
For a dropped connection, resend. For a rejected proof, rebuild. I'd copied a retry
that resent, and it couldn't have worked no matter how many times it ran.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When a public network keeps telling you "it's infra," reproduce it somewhere you
fully control.&lt;/strong&gt; The local devnet turning "the chain is broken" into "my code is
wrong" was the single most useful move of the whole sprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the real state, not the tutorial's idea of it.&lt;/strong&gt; The SDK moves fast; half my
walls were things that had quietly changed shape since the docs were written.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Sealed-bid auction (contract + deploy CLI + the full roadblocks writeup):
&lt;a href="https://github.com/tomiin/midnight-sealed-bid-commit-reveal" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnight-sealed-bid-commit-reveal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CLI wallet: &lt;a href="https://github.com/tomiin/midnight-cli-wallet" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnight-cli-wallet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Midnight Expert marketplace: &lt;a href="https://github.com/devrelaicom/midnight-expert" rel="noopener noreferrer"&gt;https://github.com/devrelaicom/midnight-expert&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're learning Midnight too: the concepts are genuinely different (DUST fees,&lt;br&gt;
nullifiers, no &lt;code&gt;msg.sender&lt;/code&gt;), but none of the walls I hit were exotic. They were&lt;br&gt;
stale state, flaky endpoints, and a fast-moving SDK. Reproduce, rebuild, and don't&lt;br&gt;
trust "it's infra" until you've seen it fail somewhere you own.&lt;/p&gt;

</description>
      <category>midnightfordevs</category>
      <category>blockchain</category>
      <category>zeroknowledge</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Privacy isn't encryption: what building on Midnight actually taught me</title>
      <dc:creator>Tminus1sec base.eth</dc:creator>
      <pubDate>Tue, 07 Jul 2026 10:11:39 +0000</pubDate>
      <link>https://dev.to/tminus1s/privacy-isnt-encryption-what-building-on-midnight-actually-taught-me-27j6</link>
      <guid>https://dev.to/tminus1s/privacy-isnt-encryption-what-building-on-midnight-actually-taught-me-27j6</guid>
      <description>&lt;p&gt;When I started building on Midnight, I had the wrong mental model, and I suspect&lt;br&gt;
most people arriving from "normal" blockchains have it too. I assumed a &lt;em&gt;privacy&lt;/em&gt;&lt;br&gt;
chain meant my data would sit on-chain, encrypted, and only the right people could&lt;br&gt;
decrypt it. A lockbox on a public ledger.&lt;/p&gt;

&lt;p&gt;That model is wrong. And if you design around it, you'll build the wrong thing.&lt;br&gt;
Here's what actually clicked for me after building a few contracts, including a&lt;br&gt;
privacy-preserving health-records system.&lt;/p&gt;

&lt;p&gt;Encryption and zero-knowledge are different tools&lt;/p&gt;

&lt;p&gt;They get lumped together, but they solve different problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption hides bytes.&lt;/strong&gt; You scramble data so only someone with the key can&lt;br&gt;
read it. The catch: whoever has the key sees &lt;em&gt;everything&lt;/em&gt;. And "encrypted, on a&lt;br&gt;
public ledger, forever" is a liability, not a feature. Keys leak. Keys get lost.&lt;br&gt;
And a ciphertext that sits on-chain for eternity is a standing bet that today's&lt;br&gt;
encryption survives tomorrow's computers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-knowledge proves statements.&lt;/strong&gt; It lets you show that something is true —&lt;br&gt;
"I'm over 18," "this vote is valid," "I'm the assigned doctor" — &lt;em&gt;without&lt;br&gt;
revealing the underlying data at all&lt;/em&gt;. No key handed over, nothing decrypted. The&lt;br&gt;
sensitive part never has to be shown to anyone.&lt;/p&gt;

&lt;p&gt;So privacy on Midnight is not "encrypt the data and put it on-chain." It's almost&lt;br&gt;
the opposite: keep the sensitive data &lt;em&gt;off&lt;/em&gt; the ledger entirely, and put only&lt;br&gt;
&lt;strong&gt;proofs&lt;/strong&gt; and &lt;strong&gt;commitments&lt;/strong&gt; on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real model: selective disclosure
&lt;/h2&gt;

&lt;p&gt;The idea that runs through everything on Midnight is &lt;em&gt;selective disclosure&lt;/em&gt;. You&lt;br&gt;
choose exactly which facts become public, and everything else simply never touches&lt;br&gt;
the chain. A couple of examples from things I built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A tip jar where the running total is public — so it's trustworthy — but each
donor is recorded only as a one-way hash. The chain never learns who gave.&lt;/li&gt;
&lt;li&gt;A vote where the tally is public but the voter is a nullifier. Reveal the count,
hide the identity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what's &lt;em&gt;not&lt;/em&gt; happening: nothing is "encrypted and then decrypted later." The&lt;br&gt;
private part is never on-chain in any form. That's the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the misconception really bites: health records
&lt;/h2&gt;

&lt;p&gt;I built an access-control contract for medical results, and it's the perfect place&lt;br&gt;
to see the wrong model fail.&lt;/p&gt;

&lt;p&gt;If you believe "privacy = encrypt on-chain," you'd try to store encrypted lab&lt;br&gt;
results on the chain and build a key-management scheme around them. You'd spend all&lt;br&gt;
your time on the vault. And you'd have put the most sensitive data humans produce&lt;br&gt;
onto a public ledger forever.&lt;/p&gt;

&lt;p&gt;Here's what I built instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The result stays encrypted &lt;strong&gt;off-chain&lt;/strong&gt;. The chain holds only a &lt;strong&gt;commitment&lt;/strong&gt;
— a one-way hash of it. Tamper-proof, but it reveals nothing.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// the lab posts only the HASH of the (off-chain, encrypted) result
resultHash.insert(disclose(recordId), disclose(resultCommitment));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Identity never goes on-chain. A caller proves they hold a secret key — a private
&lt;em&gt;witness&lt;/em&gt; — and all the chain ever stores is a hash of it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness localSecretKey(): Bytes&amp;lt;32&amp;gt;;              // private, never disclosed

export pure circuit accountId(sk: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  return persistentHash&amp;lt;Vector&amp;lt;1, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([sk]);   // one-way
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The chain enforces the &lt;strong&gt;rules&lt;/strong&gt;: only the assigned doctor with the patient's&lt;br&gt;
consent gets routine access; an emergency responder can break the glass, but&lt;br&gt;
every override is logged for audit. None of that needs the data — it needs&lt;br&gt;
proofs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And you can still prove a result is the genuine one, without the chain ever&lt;br&gt;
seeing it:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit verifyResult(recordId: Uint&amp;lt;64&amp;gt;, candidate: Bytes&amp;lt;32&amp;gt;): Boolean {
  return resultHash.lookup(disclose(recordId)) == disclose(candidate);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chain never sees your data. It enforces the rules &lt;em&gt;about&lt;/em&gt; your data, and keeps&lt;br&gt;
an honest audit trail. The data itself stays where sensitive data belongs — off&lt;br&gt;
the public ledger.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design lesson
&lt;/h2&gt;

&lt;p&gt;Once I stopped thinking "hide the bytes" and started thinking "prove the facts,"&lt;br&gt;
the designs got simpler and better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't put sensitive data on-chain, even encrypted. Put &lt;strong&gt;proofs and commitments&lt;/strong&gt;
on-chain; keep &lt;strong&gt;data&lt;/strong&gt; off-chain.&lt;/li&gt;
&lt;li&gt;Let the chain be the &lt;strong&gt;referee&lt;/strong&gt; — rules, consent, audit — not the &lt;strong&gt;vault&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Compact makes this explicit whether you like it or not: the &lt;code&gt;disclose()&lt;/code&gt; keyword
forces you to opt in every time you make something public. You can't accidentally
leak private data; you have to choose to reveal it. That constraint is a gift.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest boundary
&lt;/h2&gt;

&lt;p&gt;Midnight isn't magic, and pretending otherwise is how you ship a demo that&lt;br&gt;
overpromises. The encryption and key management for the actual data still happen&lt;br&gt;
off-chain with ordinary cryptography. What Midnight adds is the layer on top: the&lt;br&gt;
proofs, the rules, and the audit. Being clear about that line is the difference&lt;br&gt;
between a real design and a pitch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Privacy on Midnight isn't a lockbox on the chain. It's proving the truth while&lt;br&gt;
keeping the data to yourself. Once that clicked, I stopped trying to hide bytes and&lt;br&gt;
started asking a better question: &lt;em&gt;which facts actually need to be public?&lt;/em&gt; Answer&lt;br&gt;
that, put only those on-chain as proofs, and let everything else stay private by&lt;br&gt;
never being there in the first place.&lt;/p&gt;

&lt;h1&gt;
  
  
  midnight #zero-knowledge #privacy #blockchain #selective-disclosure
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Why your Midnight contract "can't be found on chain" (or: Midnight, stop playing with my emotions)</title>
      <dc:creator>Tminus1sec base.eth</dc:creator>
      <pubDate>Fri, 26 Jun 2026 01:48:18 +0000</pubDate>
      <link>https://dev.to/tminus1s/why-your-midnight-contract-cant-be-found-on-chain-or-midnight-stop-playing-with-my-emotions-18l5</link>
      <guid>https://dev.to/tminus1s/why-your-midnight-contract-cant-be-found-on-chain-or-midnight-stop-playing-with-my-emotions-18l5</guid>
      <description>&lt;p&gt;I'm a self-taught learner grinding through the Midnight quests, and this week the chain and I had a little falling out. I deployed a contract, got an address back, submitted it like a proud parent at a recital, and got served four of the saddest words in Web3: "cannot find contract on chain."&lt;/p&gt;

&lt;p&gt;My code was fine. I just didn't understand Midnight's networks and tokens yet, and a tutorial I trusted shipped me off to a network that doesn't exist anymore.&lt;/p&gt;

&lt;p&gt;So before we get into it: Midnight team, I love you, you're literally building the future of privacy, but quit teasing those of us out here learning. As the song goes - my feelings, don't play with them.&lt;/p&gt;

&lt;p&gt;Okay. Map time. This is the thing I wish someone had handed me on day one.&lt;/p&gt;

&lt;p&gt;Midnight is not one network (plot twist)&lt;br&gt;
The first thing that got me: "Midnight" isn't a single place. There are a few networks, and they do not talk to each other:&lt;/p&gt;

&lt;p&gt;A local one. You run a node, an indexer, and a proof server on your own machine with Docker (people call it "standalone" or "undeployed"). Everything here lives only on your computer.&lt;br&gt;
Preview. A public test network.&lt;br&gt;
Preprod. Another public test network, and the one a lot of the current quests and tutorials point you at.&lt;br&gt;
Mainnet. The real one.&lt;br&gt;
"It works on my machine" is the oldest excuse in software, and on Midnight it's sometimes literally true. My contract was absolutely on chain. The chain was just my laptop.&lt;/p&gt;

&lt;p&gt;Oh, and there used to be a network called testnet-02. It has been retired. It is gone. But a lot of older tutorials and example repos were written for it and still point right at its ghost.&lt;/p&gt;

&lt;p&gt;The trap: old tutorials point at a network that left the chat&lt;br&gt;
testnet-02 walked so the newer networks could run. Then it kept walking, straight out of existence. Following an older tutorial that targets it is like pulling up to the function at last year's address: lights off, cups gone, nobody's coming.&lt;/p&gt;

&lt;p&gt;Here's what actually bit me. I forked an older example dApp, followed its instructions to the letter, deployed, and my contract was nowhere. When I finally opened the project's config, every address pointed at the dead network:&lt;/p&gt;

&lt;p&gt;indexer = '&lt;a href="https://indexer.testnet-02.midnight.network/api/v1/graphql" rel="noopener noreferrer"&gt;https://indexer.testnet-02.midnight.network/api/v1/graphql&lt;/a&gt;'&lt;br&gt;
node    = '&lt;a href="https://rpc.testnet-02.midnight.network" rel="noopener noreferrer"&gt;https://rpc.testnet-02.midnight.network&lt;/a&gt;'&lt;br&gt;
Those URLs are tombstones. So before you run any older Midnight project, open its config and check which network it targets. A quick way to sniff it out:&lt;/p&gt;

&lt;p&gt;grep -rn "midnight.network" --include=*.ts .&lt;br&gt;
If you see "testnet-02" or an old path like "/api/v1," back away slowly. The live networks today are reached through "indexer.preview.midnight.network" and "indexer.preprod.midnight.network" on a newer API version. If a repo hasn't been touched since testnet-02 was alive, it probably won't land anywhere you can verify, no matter how clean your code is.&lt;/p&gt;

&lt;p&gt;"Cannot find contract on chain": the two usual heartbreaks&lt;br&gt;
Once the networks clicked, the error finally made sense. When a quest checker (or another human) can't find your contract, it's almost always one of these:&lt;/p&gt;

&lt;p&gt;You deployed to your local network. The address is real, but it lives on your machine and nowhere else. (See: "works on my machine.")&lt;br&gt;
You deployed against a dead or wrong network, so it never landed anywhere a live explorer can see.&lt;br&gt;
The fix for both is the same: deploy to a live public network (Preview or Preprod), then confirm it in a public explorer before you submit the address anywhere. If you can't find it in an explorer, the checker won't either. Trust, but verify - and in Web3, verify twice.&lt;/p&gt;

&lt;p&gt;Tokens: NIGHT, DUST, and their test-network twins (please stop trying to buy anything)&lt;br&gt;
Confession: I went hunting for the NIGHT token on an exchange so I could deploy a contract. Like a clown. You do not buy anything to build and test. But the token setup confused me for a minute, because there are four names floating around, not two. Here's the simple version:&lt;/p&gt;

&lt;p&gt;NIGHT is the real, mainnet token - staking and governance, and it's what generates the fee token.&lt;br&gt;
DUST is what actually pays transaction fees. You don't buy it; you generate it by holding and registering NIGHT.&lt;br&gt;
On a test network, both have free stand-ins: test NIGHT (tNIGHT) and tDUST. They mirror the real pair exactly.&lt;br&gt;
So the real flow for testing is: get free test NIGHT from the faucet, register it for DUST generation, and that produces the tDUST that pays your fees. You hold the NIGHT, the DUST drips out of it over time, and none of it costs you a cent.&lt;/p&gt;

&lt;p&gt;Here's the part that humbled me: I had 1000 test NIGHT sitting in my wallet, staring at "you have no DUST for gas," wondering who hurt me. The answer was me. I hadn't registered the NIGHT yet, so no DUST was being generated. Rookie move. NIGHT in the wallet is potential energy; you have to register it before the fees actually flow.&lt;/p&gt;

&lt;p&gt;The other catch that gets everyone: set your wallet to the Preprod network BEFORE you copy your address. Hand the faucet a mainnet address and it'll look at you like "I don't know her," even though it's the same wallet.&lt;/p&gt;

&lt;p&gt;The proof server (somebody's gotta do the math)&lt;br&gt;
Every Midnight transaction needs a zero-knowledge proof, and something has to actually do that homework. That something is a "proof server." You either run one yourself locally in Docker, or you use a wallet that does the proving for you (the 1AM wallet does this "dust-free," so you can skip the Docker dance). If your deploys or DUST registration just hang there spinning, the proof server and your network config are the first suspects.&lt;/p&gt;

&lt;p&gt;Big shoutout to Gutopro, who already wrote the Preprod survival guide for the exact faucet-and-sync headaches that come next - linked at the bottom. Standing on the shoulders of devs who suffered first.&lt;/p&gt;

&lt;p&gt;A short checklist before you deploy (so the chain stops ghosting you)&lt;br&gt;
Pick your network: local for solo testing, Preview or Preprod for anything others (and quests) need to see.&lt;br&gt;
Open the project config and confirm it targets that live network, not testnet-02 or an old API path.&lt;br&gt;
Fund your wallet with free test NIGHT from the faucet (wallet set to that network first), then register it so DUST starts generating.&lt;br&gt;
Make sure a proof server is available, local in Docker or via a dust-free wallet.&lt;br&gt;
After deploying, find your contract in a public explorer. There? You're golden. Not there? You're on local-only or a dead network.&lt;br&gt;
Wrapping up&lt;br&gt;
Honestly, this is still the most fun I've had while being completely confused. Privacy tech on the frontier comes with a few battle scars, and that's part of the charm. Midnight team: I'm not mad, I'm just... also a little mad. But mostly grateful, and mostly having a blast. Just, you know - don't play with my tDUST.&lt;/p&gt;

&lt;p&gt;If you're just starting out: the networks are separate, old repos may point at a dead one, and you fund testing with free test tokens, not your wallet. Get those three straight and you'll save yourself the afternoon I lost.&lt;/p&gt;

&lt;p&gt;Built on, and learning with, the Midnight community.&lt;/p&gt;

&lt;p&gt;Further reading: "Troubleshooting Midnight Pre-Prod: A Week in the Trenches" by Gutopro for the Midnight Aliit Fellowship - &lt;a href="https://dev.to/midnight-aliit/troubleshooting-midnight-pre-prod-a-week-in-the-trenches-2g4d"&gt;https://dev.to/midnight-aliit/troubleshooting-midnight-pre-prod-a-week-in-the-trenches-2g4d&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>blockchain</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title>Learning Midnight: nothing goes public by accident</title>
      <dc:creator>Tminus1sec base.eth</dc:creator>
      <pubDate>Mon, 22 Jun 2026 13:26:58 +0000</pubDate>
      <link>https://dev.to/tminus1s/learning-midnight-nothing-goes-public-by-accident-2odm</link>
      <guid>https://dev.to/tminus1s/learning-midnight-nothing-goes-public-by-accident-2odm</guid>
      <description>&lt;p&gt;I'm a self-taught enthusiast poking around the Midnight Network, and I wanted to write down one thing that finally clicked for me this week. Maybe it helps someone else who's just starting out.&lt;br&gt;
Midnight is a privacy blockchain, and you write its smart contracts in a language called Compact. Coming in, I assumed "privacy blockchain" meant everything is hidden by default and you flip a switch when you want to share something.&lt;br&gt;
It's actually the other way around, and that surprised me.&lt;br&gt;
In Compact, anything you want to put into the public, on-chain state you have to mark on purpose, with a keyword called disclose(). If you forget, the compiler stops you. It won't let you publish a value unless you've clearly said "yes, I mean to make this public."&lt;br&gt;
The first time I hit that, it felt annoying, like extra typing for no reason. Then it clicked: that IS the privacy model. Nothing leaks by accident. You go line by line and decide exactly what crosses from the private side to the public side. Everything you don't disclose just stays private.&lt;br&gt;
That made Midnight's whole "choose what you share, nothing more" idea real for me in a way the tagline never did.&lt;br&gt;
To play with it, I built a tiny app: a proof-of-age gate. You prove you're over 18 without ever showing your birth year. The birth year stays private the whole time; the only thing that becomes public is a simple "yes, this person qualifies." Same idea, share the one fact that matters and keep the rest to yourself.&lt;br&gt;
Still very early in my journey, but this one stuck with me. Most of what I picked up came straight from the docs at docs.midnight.network.&lt;br&gt;
Repo, if you want to peek: &lt;a href="https://github.com/tomiin/midnight-proof-of-age" rel="noopener noreferrer"&gt;https://github.com/tomiin/midnight-proof-of-age&lt;/a&gt;&lt;/p&gt;

</description>
      <category>midnight</category>
      <category>zeroknowledge</category>
      <category>blockchain</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
