DAI SICI Framework Wallet Explorer Blog
← Blog
Release Post-mortem 18 September 2026 · v0.4.36

We minted a supply nobody could spend.

Version 0.4.35 created 161 stablecoins and sent the entire initial supply to an address whose private key does not exist. Not lost. Not on a broken laptop. Never generated.

0.4.36 fixes it. Here is what happened, what we did, and the two user-facing bugs that came along for the ride.

0.4.35 → 0.4.36 Coordinated hard fork
01
The mistake

One constant, pointing at nobody

The chain mints each stablecoin's opening supply to a treasury address, set by a single constant in the code.

Weeks before the 0.4.35 fork, that constant was filled in with a placeholder-replacement address. A script was later added to generate a proper treasury wallet — one whose key lives in the node's keystore, exportable to the wallet app. It was run. Seven wallets came out of it.

The constant was never updated to point at any of them.

So the fork did exactly what the code told it to: it minted 161 currencies to the original address. That address had no key in any keystore on any machine — only an empty bookkeeping record the node writes when it needs to track a balance for an address it has never seen a key for.

The supply existed. It was visible on-chain. And nobody could sign a transaction to move a unit of it.

161 currencies minted 0 spendable
02
How it surfaced

Not an audit. A command that failed.

Placing sell orders as the treasury returned:

! dai977927… has no private key on this node (externally registered key)

That error is precise and it was telling the truth. Chasing it down — through both machines' keystores, the generated wallet exports, and the git history of the constant — produced the answer: the address was never anybody's to sign for.

The tooling deserves credit here. The error did not say “signing failed”. It said why, and the reason turned out to be the whole story.

03
The fix

Re-fork the genesis onto a treasury we hold

0.4.36 re-forks the genesis onto a treasury we hold the key to.

Every balance and nonce carries forward from height 1846 of the 0.4.35 chain. Fifty-three allocations, 2,529.1865 DAI, conservation verified — the sum of balances plus dust plus exclusions equals total minted, exactly.

One subtlety mattered. The old treasury already held those 161 assets on-chain, and the exporter carries existing asset balances forward while adding a fresh mint to the treasury row. Re-running it naively would have carried the old supply over and minted a new one — doubling every stablecoin in existence.

So the old address is excluded outright. Its assets go, and its 1.3 DAI goes with them, because nobody can sign for that either.

New genesis

65d201ec5791c0dd9af6d6892d4fc3ee5543f6d32177c3b2a08a66ebb720fc67
Forked from height 1846 53 allocations carried 2,529.1865 DAI, conservation verified
04
What it cost

Almost nothing, because of when it was caught

Not one order had been placed against that supply. No trades, no escrow, no counterparties. The network was still small enough that a coordinated restart was a few minutes of downtime rather than a migration event.

Catching this after 600 sell orders were live — or after a real exchange had listed against it — would have been a different article.

05
Desktop app

The bug that hid in plain sight

“View Private Key” in the desktop app had been failing with:

! Key reveal failed: prompt() is and will not be supported

The reveal was never broken. It read the key perfectly, then died displaying it. Electron does not implement window.prompt(), and the error handler relabelled that as a reveal failure.

The original choice was deliberate — a prompt shows a key selectable and copyable without leaving it in the page. The replacement keeps that property: a modal that removes itself, and the key inside it, when you close it.

The same bug was in the P2P DISPUTE button. prompt('Dispute reason:'), with no error handling at all, so it threw silently and the button did nothing. Both are fixed.

06
New

Importing a key you already have

When the wallet app registers with a node, the node stores the public key only. Such a record cannot sign. The error message says so accurately — but until now there was no way to fix it, because the only key tooling generates fresh keys and refuses to touch an address that already exists.

import-wallet-key.mjs is the missing half. Give it the private key an address was created from and it derives, seals and installs it so the node can sign.

Handling is deliberate:

  • The key is read from a file or environment variable, never from the command line — ps exposes that, and your shell records it
  • A wallet-app JSON export is accepted whole, so nobody has to fish the field out
  • It is never printed, including on error; a bad key reports its length, not its value
  • --expect <address> is mandatory and verified before anything is written
  • A record that already holds a private key is never overwritten

Also in this release

One change, and it is the build.

Builds run on Node 22

The bundler started pulling a prebuild that calls a Node-API symbol Node 20 does not export, which killed every platform build at once. CI now matches what production runs.

Update the wallet and the miner together

0.4.36 is a coordinated hard fork. Adding and moving a treasury changes the chain's starting state, so every node moves together — a node on the old genesis will be rejected by the new network rather than silently corrupted.

Update the wallet and the miner at the same time. Existing balances carry over.

What we changed about how we work

A constant pointing at the wrong address is a small bug. It produced an unspendable money supply.

The gap was not the typo — it was that nothing verified the treasury could sign before minting to it. A fork is a one-way door, and we walked through it without checking the handle on the other side.

That check now happens before the mint, not after the first failed order.

Put an idle computer to work

A Mac mini, an old gaming PC, a server with spare hours. Download it, pick a model, and it starts earning while you get on with your day.

$ curl -fsSL https://miner.iamai.kg/scripts/easy-start.sh | bash