Local stack · Undeployed · Lovable bridge

Run Midnight on your machine

The fastest way to connect a Lovable app to Midnight is to run a local standalone stack: node + indexer + proof-server in Docker on localhost. Your app runs in the browser, Lace points at ws://localhost:9944, and every SDK version is guaranteed to match the chain. No faucet, no DUST sync stalls, no Preprod quirks.

one command
bun scripts/midnight-standalone.mjs up

Requires Docker Desktop (macOS/Windows) or Docker Engine (Linux). First run pulls ~1 GB and takes 2–5 minutes; later boots are seconds.

pinned versions
proof-server:8.0.3 · midnight-node:0.22.5 · indexer-standalone:4.0.2

Do not use :latest — the midnight-node latest tag frequently 404s, and mismatched versions cause ZKIR /check 400 errors. This triple is the current local-dev combination from the Midnight Support Matrix.

  1. 1

    Docker running

    Open Docker Desktop and wait for the whale icon, or on Linux run sudo systemctl start docker. Then confirm docker info works.
  2. 2

    Start the local stack

    In your project root run:
    bun scripts/midnight-standalone.mjs up
    The script writes .midnight/standalone.docker-compose.yml, pulls the pinned images, boots the three containers, and polls until the endpoints are ready.
  3. 3

    Verify in the browser

    Open /undeployed-preflight in your Lovable app. Four green pills means the app can reach the local stack. If any pill is red, the exact endpoint and error are shown.
  4. 4

    Point Lace at localhost

    In Lace: Settings → Network → Custom → RPC = ws://localhost:9944. Lace will label the network "Preview" — that's cosmetic; the address prefix mn_addr_undeployed1… is the truth. No tNIGHT → tDUST dance here — that trap is Preview / Preprod only. The genesis wallet is pre-funded with unlimited tDUST.
  5. 5

    Deploy the contract

    VITE_NETWORK_ID=undeployed bun scripts/deploy-midnight.mjs
    The deploy script builds a headless wallet from the genesis seed 0x000…0002 directly — Lace isn't required for the deploy itself. Paste the printed hex address into VITE_DEFAULT_CONTRACT in your Lovable secrets. Grab the lovable-midnight skill → to bake all nine deploy-script rules into your own Lovable account.
  6. 6

    Wire the wallet UI (optional)

    For the Connect-Lace button, RPC-mode toggle, and shielded/unshielded address readout, copy the boilerplate from /wallet. It handles the DApp Connector v4 handshake and enumerates wallets by apiVersion.
Lovable secrets
VITE_NETWORK_ID=undeployed
VITE_INDEXER_URL=http://localhost:8088/api/v4/graphql
VITE_INDEXER_WS_URL=ws://localhost:8088/api/v4/graphql/ws
VITE_NODE_RPC=ws://localhost:9944
VITE_PROOF_SERVER_URL=http://localhost:6300
local endpoints
node · ws://localhost:9944
indexer · http://localhost:8088/api/v1/graphql
proof · http://localhost:6300
alternative · no docker · community

Skip Docker with Effectstream

effectstream/effectstream is a community chain-abstraction orchestrator that wraps the Midnight node, indexer, and proof server as plain npm binaries — no Docker Desktop, no WSL, no BIOS virtualization. One bunx command supervises the whole dev stack (node + indexer + proof server + your deploy step + frontend) with status and logssubcommands. If Docker is fighting you on Windows, this is the fastest path back to building.

1 · Install the binaries

bun add -d @effectstream/midnight-node \
  @effectstream/midnight-indexer \
  @effectstream/midnight-proof-server \
  @effectstream/orchestrator

Same binaries the Midnight team ships — just resolved through npm instead of Docker Hub.

2 · Run the dev loop

bunx orchestrator start --background
bunx orchestrator status
bunx orchestrator logs
bunx orchestrator stop

Once running, point VITE_INDEXER_URL, VITE_PROOF_SERVER_URL, and node RPC at the local ports the orchestrator prints on start.

Reference templates worth reading

  • templates/evm-midnight-v2 — EVM + Midnight, ERC-721 sync, ZK contracts, full React frontend. Closest match to a hackathon dApp that spans two chains.
  • templates/zswap-da — Midnight Zswap for decentralized liquidity. Good reference for anyone building token-flow demos on Undeployed.

Community project — not an official Midnight release. If a binary version drifts from the support matrix, fall back to the Docker Compose recipe above.

optional · public demo · fly.io

Host the same stack on Fly.io

Local Docker is perfect for solo dev. But when a judge or teammate needs to hit your dApp with Lace from their own laptop, you need the Undeployed stack on public infra. Here's the recipe distilled from the Tokenized Choreo Kits build — including the blocker that's still open.

1 · four-app topology
  • choreo-node — Midnight standalone node, 6PN-internal only, reached as ws://choreo-node.internal:9944. No public HTTP.
  • choreo-indexer — public HTTPS + WSS on /api/v4/graphql.
  • choreo-proof — public HTTPS proof server.
  • choreo-faucet — public HTTPS /grant endpoint, in-memory rate limit, pre-funded once from the genesis seed.

Bootstrap shape: scripts/fly-bootstrap.sh creates all four apps + a 1 GB volume for the node, sets FAUCET_SEED, and deploys.

2 · non-obvious gotchas
  • Proof server needs memory = "2gb" — the k=13 proving key OOMs on 1 GB mid-mint.
  • auto_stop_machines = false on proof + node. Cold start is ~4 min of user-visible "Proving…".
  • Proof-server binary is IPv4-only. Fine as-is via the public https://…fly.dev URL (fly-proxy enters over IPv4). If you ever need 6PN access, add a socat sidecar binding [::]127.0.0.1.
  • Node RPC must bind IPv6 or the indexer/faucet can't reach it via .internal:
    [processes]
    app = "--experimental-rpc-endpoint \"listen-addr=[::]:9944,methods=unsafe\""
  • Never scale choreo-node above 1 machine — two machines = two participants = forked chain. Run flyctl scale count 1 after every deploy.
  • No [http_service] on the node — expose port 9944 via [[services]] only, so it stays 6PN-internal.
  • Pin midnight-node:0.22.5. Do not bump to 2.x — those are Partner Chain builds that need Cardano db-sync and crash-loop on standalone.
  • Persistent chain_data volume (1 GB) on the node — wipe it and every previously-deployed contract address becomes invalid.
  • Faucet wallet has to be pre-funded once from the genesis seed …0002 before /grant works. Hit /health to see the balance while it syncs.
⚠ open blockerstandalone node stuck at block #0

On Fly, midnight-node:0.22.5 with CFG_PRESET=dev + SIDECHAIN_BLOCK_BENEFICIARY=<hex> boots in partner-chain mode, not standalone sealer mode. Logs show Idle (0 peers) forever and one line: Failed to trigger bootstrap: No known peers.

Downstream effect: the faucet wallet (buildFromSeed) never finishes sync, getUnshieldedAddress() returns null, and /grant returns 503 faucet warming up. Proof and indexer are healthy in this state (/version 8.0.3; GraphQL { __typename } responds on /api/v4/graphql) — the blocker is block authoring, not plumbing.

Working theory: SIDECHAIN_BLOCK_BENEFICIARY alone flips the image into partner-chain expectations. A standalone --dev sealer needs a different env combination, or the entrypoint expects a flag that [processes] in fly/node/fly.toml is currently overriding.

Next probe: flyctl ssh console -a choreo-node, dump /entrypoint.sh and the image's supported env vars, then diff against midnight-local-dev/standalone.yml, which authors blocks fine locally with the same tag.

Workaround while unresolved: use the local Docker stack above for Undeployed. Fly hosting only unlocks once the standalone sealer boots.

3 · deploy flow (once the blocker clears)
  1. scripts/fly-bootstrap.sh — creates the 4 apps + volume + secrets, deploys all four.
  2. Fund the faucet once by sending tDUST from the genesis deployer wallet (seed …0002) to the address shown by /health.
  3. scripts/fly-deploy-contract.sh runs on a Fly Machine (so it reaches choreo-node.internal over 6PN) and prints the contract address.
  4. Paste that hex into your app's VITE_DEFAULT_CONTRACT env var and republish.
4 · when to reach for fly vs local docker

Local Docker for offline dev, preflight, and iteration. Fly when you need a publicly demoable dApp that any judge with Lace can hit from their own laptop.

⚠ Fly is public infra. Treat FAUCET_SEED like a real key, keep the /grant rate limit on, and never point Lace at the hosted node from a Mainnet account.