Moth: a Wallet for Midnight Developers
Code first. The reasoning is below:
git clone https://github.com/shieldedtech/moth-wallet && cd moth-wallet
yarn install && yarn build
alias moth="node $PWD/packages/cli/bin/moth"
moth wallet generate --name alice --network preprod
moth balance --wallet alice --network preprod
The extension is the same repository: yarn workspace @shieldedtech/moth-extension build, then load packages/extension/.output/chrome-mv3 unpacked from chrome://extensions with developer mode on. There is no store listing, and there will not be one for a while.
There is a family of moths, Catocala, that entomologists call the “underwings.” At rest they are almost impossible to see. The forewings are like bark: grey, brown, scribbled with the exact noise of lichen on an oak trunk. A moth sitting still on that trunk looks like a piece of the trunk. Then something disturbs it, the forewings lift, and underneath is a band of scarlet or orange so sudden that a bird pulls up mid-strike. By the time it recovers, the moth has landed somewhere else and has become bark again.
The interesting part is not the camouflage. It is that the moth carries both at once, and chooses when to show itself.
That is roughly the shape of the problem Midnight hands to a DApp developer. A Midnight wallet is not one balance. It has a shielded account, an unshielded account, and a DUST address. DUST is the resource that pays for computation: you “generate” it by holding NIGHT, and you cannot send it to anyone. Some of what you hold is bark. Some of it is brightly coloured. Which is which is a decision your application makes, over and over — and getting it wrong is not a rendering bug. It is a privacy breach.
Moth is a wallet for the people making those decisions.
Not a consumer wallet
To be very clear: Moth is not trying to be the wallet your users will install. It is the wallet you run while building the thing your users will install. It defaults to a Midnight test network (preprod), not mainnet. It is unaudited and unsupported. It exposes internals a consumer wallet is right to hide — per-coin registration state, request counters, phase timings, raw circuit plumbing.
Most wallets are designed so you never have to think about what they are doing. Moth is designed for the fifteen minutes when you very much do.
It is also Midnight-only. It does not speak to Cardano, it has no idea what cNIGHT is, and it will not become a multi-chain wallet. Native NIGHT and DUST on Midnight, and nothing else.
Everything below is a shell around one shared engine: core, which holds the key derivation, the encrypted keystores, the network clients, the proof orchestration and the transaction building, with no platform-specific imports at all. The four surfaces are thin layers on top. They differ in how you talk to them, not in what they know.
The extension
The browser extension is the surface a DApp actually meets. It implements the Midnight DApp Connector API, so your application can request addresses, balances, transaction history, signatures and proving through a standard interface rather than something bespoke.

Moth is a Manifest V3 extension, which means the interesting engineering is all about where things are allowed to run. Wallet logic needs WebAssembly and long-lived state, and a service worker reliably gives you neither. So the wallet host lives in an offscreen document with a dedicated worker, and the service worker is reduced to a message router that Chrome can kill and restart without losing your sync.
Day to day you see a panel: balances split shielded and unshielded, DUST generation with its fill rate, recent activity, and an approval flow that shows what a DApp is asking for before you agree to it.
There is also a debug page, reachable directly by URL and linked from nothing, that reports phase timings and per-host request counts with outcomes. That page exists because one of us at Shielded — ok, it was me — kept getting HTTP 403s from a node that nobody else could reproduce, and “it feels like a lot of requests” is not something you can take to the people who run the endpoint. So I built something that produces a number instead.
The number turned out to be mine. I had left a docker container running an experimental indexer, and it had been hammering the node all along. PEBCAK. Apologies to the Shielded SRE team — though I would gently point out that the tool worked exactly as intended, just not in the direction I was hoping.
The CLI
moth is the same wallet with no graphical interface at all. This is the one you want in CI and in shell scripts.
One caveat before you reach for it. A brand-new wallet is not usable the moment you create it: the DUST sub-wallet has to reconstruct generation state by replaying the ledger events that produced it, and on a chain with real history that takes a while. That is a property of where zero-knowledge chains are today rather than a fault, and the protocol roadmap addresses it later this year. In the meantime Moth can skip the part of that replay which is identical for everyone — a pre-seed reference, one unfunded throwaway wallet’s synced state captured at a known height, which every wallet created afterwards can start from. The extension ships references for preprod and preview, so a wallet you create there starts near the head of the chain rather than the beginning of it. On the CLI and TUI the same machinery currently sits in a parity branch rather than the formal release, so if you want it on those surfaces you are building from a branch. Fortune favours the bold.

The CLI is a single binary with the operations you would expect — balance, transfer, deploy, call, mint, state, info — plus topics for the things that need grouping: moth wallet … for keystore management, moth dust … for generation and registration, moth maintenance … for verifier-key updates on deployed contracts.
Each invocation is self-contained by default: unlock, sync, do the thing, exit. This is the right model for a one-shot script and exactly the wrong one for a test suite running forty transactions, which is what the daemon (see below) is for.
The TUI
The terminal dashboard is the surface that makes state visible.

It is a React application rendered with Ink, organised as screens: a dashboard with a wallet-state view, send, deploy, mint, contract inspection, DUST management, keys, network configuration and logs.
This is where the per-coin detail lives, and that turns out to matter more than we expected when we built it. A wallet can show you 500 NIGHT and still refuse to register any of it for DUST generation, because the displayed balance folds in coins that are booked as inputs by a transaction that has not settled yet. Both numbers are correct. Neither explains the other. The dashboard is where you can see which coins are registered, which are booked, and therefore why the wallet is telling you something that looks like a contradiction.
When a balance surprises you, the question is almost never “what is the total.” It is “which coins, in what state, and since when.”
This is the bark-and-scarlet question, asked of every coin you hold, and it is the one a wallet built for end users is designed never to make you think about.
The daemon
The daemon is what makes the other three usable together.
Run the TUI, or run moth daemon serve headless, and one long-lived process owns the wallet and keeps its state warm. CLI subcommands under moth daemon … route to it over a Unix domain socket instead of starting their own. A test suite doing forty operations pays the startup cost once rather than forty times, and two commands touching the same wallet stop racing each other.

The socket is the security boundary, so it is worth saying exactly how it is drawn. It lives at ~/.moth/sync/<network>/<wallet>.sock, mode 0600 inside a 0700 directory — the kernel enforces that only your UID can connect, before any wallet code runs at all. On top of that, every write verb (transferTokens, deployContract, callCircuit, dustRegister, insertVerifierKey, and the rest) raises a confirmation modal in the TUI showing the operation, the recipient or contract, and the amounts. Nothing moves without a human in the loop.
Headless automation obviously cannot show anyone a modal, so it can auto-approve — but only when --auto-approve and MOTH_DAEMON_AUTO_APPROVE=1 are both set. Two independent switches, deliberately, so a flag left in shell history cannot quietly disarm consent on a service account. Use it at your own risk, and preferably on a wallet you would not mind losing.
The daemon today is local-only by design: one machine, one user, kernel-enforced. There is a spec in the repository describing what a multi-tenant, network-accessible version would need — authentication, policy, audit, a real threat model. We wrote it down precisely because those are not things you add casually to something holding keys.
A word about the logs
One more thing, because it says something about how this project treats you.
For a while, a DUST registration that registered nothing would write tx: register complete to the timings log. Not a lie exactly — the call had returned without throwing — but the wallet had submitted no transaction at all, and the log said the same thing it says on success. I spent an afternoon debugging the network before noticing that the operation I was investigating had never happened.
It now says no-op (no available unregistered NIGHT), and it distinguishes that from no-op (fee not affordable yet), because those have different causes and different fixes.
That is the standard we are trying to hold: a developer wallet that tells you a comfortable thing is worse than no wallet at all. If you catch Moth doing it, that is a bug, and we would like to hear about it.
Where it is
Moth is Apache-2.0, and it lives at github.com/shieldedtech/moth-wallet.
It is experimental. It is unsupported. A fresh install lands on a test network, and we would rather you kept it there for now.
But if you are building on Midnight and you have reached the point where you need to know not just what your balance is but why — which coins, in what state, disclosed to whom — that is the point Moth was built for. The wallet carries both wings. Your job is choosing which one is showing.