Error 170: The Night Midnight Told me No!
What a failed deploy taught me about how blockchains actually charge you.
There’s a specific kind of silence that happens at 2 a.m. when a computer refuses to do the one thing you asked it to do, and gives you a number instead of a reason.
Mine was 1010: Custom error 170.
I had written a smart contract. A sealed-bid auction, where everyone submits a hidden bid, no one can peek, and the winner is revealed at the end. On a normal blockchain that’s close to impossible to do because everything is public. On Midnight, it’s the whole point. I was proud of it.
And I could not get it onto the network.
Not “it deployed and behaved strangely.” Not “the logic was wrong.” It never landed at all. Fifteen, twenty times, I lost count. Same number every time. 170.
I want to walk you through that night, because buried in it is one of the smarter things about how Midnight works, and one of the most useful debugging lessons I’ve picked up as a self-taught developer.
First, a thing about gas nobody tells beginners
On most blockchains, paying a transaction fee is simple to describe. You hold the network’s token and some of it gets consumed when you do something. Ethereum has ETH, you pay gas in ETH. One token doing two jobs: the thing you own and the thing you spend.
Midnight however, splits those jobs in half.
There’s NIGHT, which is the asset you hold. And there’s DUST, which is what actually pays for transactions. You don’t buy DUST. Holding NIGHT generates it for you, slowly, over time, and it gets consumed when you transact. Think of a battery that recharges as long as you’re holding the thing that charges it.
The first time I read that, it sounded like complexity for no reason. Two tokens where one would do. Why?
Then it clicked. Fees are a privacy leak.
Think about it. On a transparent chain, the fee you pay is public, the token you paid it with is public, and the balance you paid it from is public. Even if the contents of your transaction were somehow private, the payment for that transaction is a bright, traceable thread running straight back to your wallet. You’ve hidden the letter and left your fingerprints on the envelope.
Separating the fuel from the asset cuts that thread. DUST gets spent and regenerates instead of being debited from something that identifies you. It’s also shielded, so the fee itself doesn’t broadcast metadata about who you are.
I fully appreciated all of this at around hour four, while it was busy refusing to let me deploy anything.
What error 170 actually means
Here’s the part that took me all night to understand.
Every transaction on Midnight carries a small proof saying the fee has been legitimately covered. Not a promise. A piece of cryptographic evidence, generated at the moment your transaction is assembled.
Error 170 means the network looked at that proof and rejected it.
Notice what that doesn’t say. It doesn’t say your contract is broken. It doesn’t say your logic failed. My auction code was fine the entire time. The rejection was about the envelope, not the letter.
And here’s the cruel detail. That proof is tied to a moment in time. The blockchain keeps moving forward, adding blocks. If your transaction is built at one moment and arrives after the chain has moved on, the proof it carries can be stale. Technically valid when it was made, no longer valid on arrival.
You’ve mailed a letter with correct postage that expired in transit.
The bug was mine
Naturally, I had written a retry loop. When something fails, you try again. That’s programming 101.
So my code caught the failure and resubmitted. Failed. Resubmitted. Failed. Twenty times, patiently, automatically, uselessly.
Because here’s what I didn’t understand. The stale proof was baked into the transaction itself. Every retry was re-sending the exact same envelope with the exact same expired postage. I wasn’t trying again. I was trying the same thing again, faster, and expecting a different answer.
The fix was to rebuild the transaction from scratch on every attempt. New transaction, fresh proof, fresh postage, instead of re-sending the dead one.
That distinction is now permanently wired into my brain, and it goes well beyond Midnight:
A retry only helps if the next attempt is actually different from the one that failed. Dropped connection? Resend, the request was fine and the network just fumbled it. Rejected proof? Rebuild, because the request itself is the problem and sending it again changes nothing.
Nobody teaches you that difference. You learn it at 2 a.m.
The detour that saved me
Before I found the bug, I spent hours convinced it wasn’t my fault.
This is the part I’d rather not admit, and it’s the most useful part. My reasoning went like this: the error is about network state, my code looks reasonable, so the network must be lagging. Public test networks have hiccups. It’s probably them.
So I went looking for a cleaner network. I tried another testnet and itts faucet locked me out for twenty-four hours. Two networks, two dead ends, same night, and I still had nothing deployed.
Then I did the thing I should have done at hour one. I stopped blaming the environment and built one I controlled.
I spun up a complete Midnight network on my own laptop. The node, the indexer, all of it, running locally in Docker. No public infrastructure. No lag between components. A chain perfectly in sync with itself, with nobody to blame but me.
It threw error 170 immediately.
That was the moment the night turned around. If a network that cannot be lagging still rejects my transaction, then the problem was never the network. It was in my code, where it had been the whole time. Everything after that was just finding it.
That’s the lesson I’d hand to anyone learning this stuff:
When a system tells you “it’s the infrastructure,” reproduce the failure somewhere you fully control before you believe it.
It costs an hour but it saves you from spending five investigating an innocent party. That’s not a blockchain lesson, it’s a debugging lesson that happens to show up brutally in blockchain, because there are so many moving parts to wrongly accuse.
And then it deployed
Fixed the retry logic. Rebuilt instead of resent.
It failed on a weak password. The tooling wanted something stronger for the private-state encryption, which is a completely reasonable thing to want and an absolutely deflating thing to discover at 4 a.m. right after you’ve solved the real problem. I changed the password.
Then it went through.
A sealed-bid auction, live on the network. Bids that stay hidden until reveal, enforced by math instead of by trusting an auctioneer. It exists on-chain and I can point at it.
What I actually took away
The contract wasn’t the hard part. Writing the auction logic took maybe two hours. Ten hours went to getting it onto the network: fees, proofs, timing, and my own misplaced confidence about whose fault it was.
I don’t think that ratio is unusual, and I don’t think anyone warns you about it.
One thing I keep coming back to is this. The whole frustrating fee system, the one that cost me a night, exists because someone decided that even paying for privacy shouldn’t cost you your privacy. They could have used one token and shipped faster. They took the harder road so the fee wouldn’t become the leak.
You just have to fight it a little before you can see it.
Next time I’ll deploy in twenty minutes and I’ll know exactly why the envelope matters as much as the letter.




Great post! I actually didnt know that Every transaction on Midnight carries a small proof saying the fee has been legitimately covered!
The one thing I love about Midnight is how technologically advanced the blockchain is! It really is built for fixing everything!