Skip to main content

Midnight Kitties Tutorial: A Wallet Incompatibility That Blocks Deployment

· 4 min read
Frederico Santana
Founder & Technical Writer, DPO2U

The Midnight Kitties DApp tutorial promises a hands-on introduction to building on Midnight Network. Fork, install, build, deploy. Simple enough. But when we tried to use our Lace wallet to deploy the NFT contract, we hit a fundamental incompatibility between two wallet SDKs that completely blocks the flow.

This post documents the problem, every workaround we attempted, and what needs to change.

The Setup

We followed the Midnight Kitties tutorial:

  1. Fork the repository
  2. yarn install and yarn build (with necessary TypeScript fixes)
  3. Run the interactive CLI to deploy on testnet

The problem surfaces at step 3: the CLI asks for a 32-byte hex seed, but the Lace wallet uses a 24-word mnemonic with an entirely different HD derivation path.

Two SDKs, Two Worlds

Lace Wallet (new SDK)

import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
import { mnemonicToSeedSync } from '@scure/bip39';

const seed = mnemonicToSeedSync(mnemonic); // 64 bytes
const hdWallet = HDWallet.fromSeed(seed);
const keys = hdWallet.selectAccount(0)
.selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
.deriveKeysAt(0);

Lace derives 3 separate keys via BIP32-Ed25519:

  • Zswap — shielded transactions
  • NightExternal — unshielded address (where faucet tDUST lands)
  • Dust — dust transactions

Kitties CLI (legacy SDK)

import { WalletBuilder } from '@midnight-ntwrk/wallet';

const wallet = await WalletBuilder.build(
indexerUri, indexerWsUri, proverServerUri, nodeUri,
seed, // raw 32-byte hex string — no HD derivation
networkId, logLevel
);

The legacy WalletBuilder treats the seed as a direct input — no HDWallet, no Roles, no derivation.

What We Tried

We attempted 4 different approaches to convert the Lace mnemonic into something the Kitties CLI would accept:

MethodSeed ProducedResult
mnemonicToEntropy (BIP39)bbc349e1... (32 bytes)Different address
PBKDF2 (BIP39 standard)c5f69a00... (32 bytes)Different address
Full PBKDF2c5f69a00...cb09... (64 bytes)SDK rejects: "Expected 32-byte seed"
HDWallet → Roles.Zswap keybf25eb44... (32 bytes)Unshielded address matches, shielded doesn't

Attempt 4 was the most promising. Using wallet-sdk-hd from our DPO2U project, we derived the correct Zswap key — confirmed because the unshielded address (mn_addr_preprod1tdg2e56...) matched the Lace wallet exactly. But the legacy WalletBuilder processes that seed differently and generates a different shielded address with 0 balance.

The funds are sitting in the Lace wallet. The CLI wallet is empty. There's no bridge between them.

Additional Issues

1. Wrong network in default config

The Kitties CLI defaults to testnet-02:

indexer: https://indexer.testnet-02.midnight.network/api/v1/graphql
node: https://rpc.testnet-02.midnight.network

But Lace runs on preprod:

indexer: https://indexer.preprod.midnight.network/api/v3/graphql
node: wss://rpc.preprod.midnight.network

Note the API version difference as well: v1 vs v3.

2. TypeScript build errors

auto-deploy.ts imports symbols that node-api doesn't re-export:

error TS2305: Module '"@repo/kitties-api/node-api"' has no exported member 'TestnetRemoteConfig'.
error TS2305: Module '"@repo/kitties-api/node-api"' has no exported member 'createLogger'.
error TS2305: Module '"@repo/kitties-api/node-api"' has no exported member 'contractConfig'.

3. Proof server download is painfully slow

The midnightnetwork/proof-server:4.0.0 (used by Kitties) downloads ~30MB of cryptographic parameters on first run at ~100KB/s. In our case, the download corrupted and required a restart. The v7.0.0 proof server we already had cached on our DPO2U VPS started instantly.

The public proof server at https://lace-proof-pub.preprod.midnight.network is a much better default.

The Impact

A developer following the tutorial:

  1. Creates a Lace wallet
  2. Gets tDUST from the faucet
  3. Clones and builds the Kitties DApp
  4. Runs the CLI
  5. Cannot use their Lace wallet — the CLI generates a different wallet with 0 balance
  6. Has no way to transfer tDUST between wallets (different shielded addresses)

The tutorial becomes impossible to complete with the wallet that the Midnight ecosystem itself recommends.

Proposed Solutions

  1. Migrate the Kitties CLI to wallet-sdk-hd — accept mnemonic input with proper HD derivation matching Lace
  2. Update defaults to preprod — testnet-02 appears deprecated
  3. Default to the public proof server (https://lace-proof-pub.preprod.midnight.network) to avoid Docker and slow downloads
  4. Document the incompatibility clearly in the README until it's resolved

What's Next

We've filed an issue on the repository with full reproduction steps. We'll keep investigating whether there's a way to bridge the two SDKs and will update this post as things evolve.

The Midnight Network is in active development, and SDK fragmentation like this is a growing pain — but it needs to be resolved for the onboarding experience to work end-to-end.


Tested on 2026-03-04 with midnight-kitties@main, Lace wallet on preprod, proof-server v7.0.0, Node.js v22.22.0.