{"slug": "midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-me", "title": "Midnight sprint: a sealed-bid auction, a stubborn error 170, and everything it taught me\"", "summary": "A developer built a sealed-bid auction smart contract on the Midnight blockchain using the Midnight Expert plugin suite within Claude Code. The contract uses domain-separated nullifiers for privacy and double-bid prevention, and was deployed on a local devnet after persistent 'Error 170' fee issues on public test networks. The developer resolved the error by running a local node with zero lag between indexer and node.", "body_md": "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\n\ndocs, and I break things constantly. So this is a build story from that seat — the\n\nMidnight Expert sprint, everything I shipped, and every wall I hit getting there.\n\nIf you're also learning this stuff, the messy parts below are the useful parts.\n\nThe whole sprint ran through the **Midnight Expert** plugins inside **Claude Code**\n\n— a marketplace of AI agent plugins for the Midnight blockchain (Compact contracts,\n\nDApp frontends, toolchain, an error-code lookup, and more). I installed it there,\n\nran the diagnostics, and did the contract + tooling work from that setup. The\n\nfinishing and debugging happened in a mix of Claude Code and Claude's desktop\n\nagent, but the Midnight-specific muscle came from those plugins.\n\nFirst three quests were the warm-up: explore the marketplace, star the repo, and\n\ninstall + run diagnostics. In Claude Code that last one is a single command:\n\n```\n/midnight-expert:doctor\n```\n\nIt checks your compiler version, tooling, and MCP servers and tells you what's\n\ngreen. Mine flagged that my Compact compiler had upgraded (0.30 → 0.31.1) and a\n\ncouple of harmless warnings. Good baseline before touching anything real.\n\nThe idea: a **sealed-bid auction**. While bidding is open, nobody — not other\n\nbidders, not even the auctioneer — can see what anyone bid. When bidding closes,\n\npeople reveal their numbers, the contract checks each against what was locked in\n\nearlier, and the highest honest bid wins. Think sealed envelopes: shut while you're\n\nbidding, opened at reveal time.\n\nThe part I actually wanted to get right was the **nullifier**. Compact has no\n\n`msg.sender`\n\n— there's no built-in \"who's calling.\" So a caller proves who they are\n\nby knowing a secret key that never leaves their machine, and the contract derives a\n\none-way fingerprint (a nullifier) from it. Two details make it real: it's\n\n**domain-separated** (tagged `\"sbid:v1:nullifier\"`\n\nso it can't be confused with any\n\nother hash from the same key), and it's the **double-bid guard** (your commitment is\n\nfiled under your nullifier, so a second bid collides and bounces). One identity, one\n\nbid, and your identity never hits the chain.\n\nI wrote it with the `compact-core`\n\nplugin, compiled it, and got a passing test suite\n\nbefore going anywhere near a network. The honest boundary, which a lot of \"private\"\n\ndemos skip: the bid *amounts* are private while bidding is open, but the\n\nnullifiers and commitments are public — that's what makes the anti-double-bid check\n\nwork.\n\nThis is where I lost a night, so buckle up. The quest wants the contract deployed to\n\n**Preprod** (a public test network) with at least one real on-chain interaction.\n\nMy deploy CLI would build the transaction, prove it, submit it — and the node would\n\nspit back:\n\n```\n1010: Invalid Transaction: Custom error: 170\n```\n\nEvery single time. Error 170 is `InvalidDustSpendProof`\n\n. Here's the thing I didn't\n\nunderstand at first: on Midnight you don't pay fees with the main token (NIGHT). You\n\nregister NIGHT to *generate* a fee resource called **DUST**, and every transaction\n\nproves a little DUST spend to cover its fee. **170 is the fee leg getting rejected**\n\n— nothing to do with my contract.\n\nI assumed it was the test network being slow (a Midnight dev even confirmed that\n\nversion of it happens when the public indexer lags behind the node). So I tried the\n\nother public network, Preview. Its faucet's human-check spun forever and then locked\n\nme out for 24 hours. Two public networks, two different dead ends, same night.\n\nSo I did the thing that actually cracked it: I stood up a **local devnet** with the\n\n`midnight-tooling`\n\nplugin — a node and indexer in Docker, right on my machine, with\n\nzero lag between them. And it *still* threw 170. That was the lightbulb. If a chain\n\nthat's perfectly in sync with itself rejects my transaction too, the problem isn't\n\nthe network — it's my code.\n\nThree things had to be fixed, in order:\n\n**1. Rebuild the transaction on every retry — don't resubmit the same one.** My retry\n\nloop was resubmitting the *exact same* proven transaction each time. But a 170's\n\nstale DUST proof is baked into that transaction, so re-sending it just re-presents\n\nthe same dead proof. On a chain minting blocks every few seconds, it's stale the\n\ninstant it's built. The fix was to rebuild and re-balance the whole thing on each\n\nattempt so every try carries a fresh proof:\n\n```\nAttempt 1: building (fresh dust balance), proving, submitting…\n  attempt 1 failed: Transaction submission error   (that's the 170)\nAttempt 2: building (fresh dust balance), proving, submitting…\nTransfer submitted. Tx: 0087338e7833176e...c008fe3d\n```\n\nAttempt two, fresh rebuild, straight through. (I found this on the wallet first, then\n\ncarried it to the deploy.)\n\n**2. Keep the wallet's DUST synced to the current tip.** Even with the rebuild loop,\n\nif the wallet's DUST state is hours behind the chain it can't even balance the fee —\n\nyou get instant \"could not balance dust\" failures instead of 170. My deploy wallet\n\nwas restoring from a checkpoint saved earlier in the day. I re-synced it to the\n\ncurrent tip first, then deployed.\n\n**3. Use a strong local password.** Once the DUST was sorted, the deploy got all the\n\nway to storing private state and died with `PasswordValidationError: Password must`\n\n. The SDK now\n\ncontain at least 3 of: uppercase, lowercase, digits, special. Found: 2\n\nenforces that on the local private-state store. Bumped my dev password to four\n\nclasses and moved on.\n\nAnd then, finally:\n\n```\n✅ DEPLOYED. Contract address: ad08e233a172874748b05ab40a30c9217699650115aa5650c3c671accfee4244\nPlacing one sealed bid (on-chain interaction)…\n✅ placeSealedBid submitted.\n```\n\nLive on Preprod, with a real interaction. After all that, it went through on the\n\nsecond attempt.\n\nThe Extend quest wants a real PR back to the [midnight-expert\nrepo](https://github.com/devrelaicom/midnight-expert). I had the perfect thing,\n\nThe repo has a `midnight-status-codes`\n\nplugin — a searchable catalog of every\n\nMidnight error code. I looked up 170, and its only suggested fix was:\n\n\"Regenerate the dust spend proof using the proof server\"\n\nWhich is exactly the trap I'd fallen into. Re-proving or resubmitting the *same*\n\ntransaction can never clear a 170 — that's the whole lesson I'd just paid for in\n\nhours. So I expanded the entry with the remediation that actually works: sync the\n\nDUST to the tip, rebuild-don't-resubmit on 170, and the practical tell (instant\n\n\"could not balance dust\" = too stale to balance, resync; a 170 *after* proving = the\n\nblock-advance race, which rebuilding rides through).\n\nTheir repo has a schema check and a test suite, and both pass with the change:\n\n```\nSchema check PASSED\nResults: 14 passed, 0 failed\nAll checks passed.\n```\n\nSmall, genuine, and something I could only have written by living through it.\n\nIf you're learning Midnight too: the concepts are genuinely different (DUST fees,\n\nnullifiers, no `msg.sender`\n\n), but none of the walls I hit were exotic. They were\n\nstale state, flaky endpoints, and a fast-moving SDK. Reproduce, rebuild, and don't\n\ntrust \"it's infra\" until you've seen it fail somewhere you own.", "url": "https://wpnews.pro/news/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-me", "canonical_source": "https://dev.to/tminus1s/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-taught-me-24kd", "published_at": "2026-07-19 01:46:51+00:00", "updated_at": "2026-07-19 02:29:04.168769+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Midnight Expert", "Claude Code", "Midnight blockchain", "Compact", "Preprod", "Preview", "Docker"], "alternates": {"html": "https://wpnews.pro/news/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-me", "markdown": "https://wpnews.pro/news/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-me.md", "text": "https://wpnews.pro/news/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-me.txt", "jsonld": "https://wpnews.pro/news/midnight-sprint-a-sealed-bid-auction-a-stubborn-error-170-and-everything-it-me.jsonld"}}