DEV Community

Cover image for Vibe Code Your First Midnight dApp with AI Agent Skills

Vibe Code Your First Midnight dApp with AI Agent Skills

Your AI coding assistant doesn't know Compact. It knows TypeScript, Solidity, Rust, but Midnight's ZK smart contract language didn't exist when most models were trained. Ask it to write a Compact contract and you'll get plausible-looking code that won't compile.

midnight_agent_skills is a set of 4 agent skills that fix this. Install them once, and your AI assistant has accurate knowledge of Compact syntax, the Midnight SDK, network config, and the gotchas that trip up real builders.

This is a walkthrough of using those skills to build your first Midnight dApp.


What are agent skills?

Agent skills are structured knowledge files that AI coding assistants load at context time. Instead of relying on training data, the assistant reads the skill files directly, so it gets accurate, current information rather than hallucinated guesses.

The midnight_agent_skills package has 4 skills:

  • midnight-concepts: ZK architecture, DUST/NIGHT tokenomics, Kachina protocol
  • midnight-compact: Compact language, circuits, ledger operations, best practices
  • midnight-api: SDK integration, wallet connection, contract deployment
  • midnight-network: Node setup, Docker, indexer, proof server

Step 1: Install the skills

npx skills add https://github.com/mzf11125/midnight_agent_skills
Enter fullscreen mode Exit fullscreen mode

Or pick individual skills:

npx skills add https://github.com/mzf11125/midnight_agent_skills --skill midnight-compact
npx skills add https://github.com/mzf11125/midnight_agent_skills --skill midnight-api
Enter fullscreen mode Exit fullscreen mode

Step 2: Start the proof server

Midnight generates ZK proofs client-side, your data stays on your machine. You need a local proof server running before you can deploy anything.

docker run -p 6300:6300 midnightnetwork/proof-server -- \
  'midnight-proof-server --network preprod'
Enter fullscreen mode Exit fullscreen mode

Check it's up:

curl http://localhost:6300
# We're alive 🎉!
Enter fullscreen mode Exit fullscreen mode

Step 3: Scaffold your project

npx create-midnight-app my-first-dapp
cd my-first-dapp
Enter fullscreen mode Exit fullscreen mode

Step 4: Write your contract

Open your AI assistant and ask it to write a Midnight contract. With the skills loaded, it knows the correct syntax.

Here's a simple owner-gated counter, only the deployer can increment it:

pragma language_version >= 0.20;
import CompactStandardLibrary;

export ledger counter: Counter;
export ledger owner: Bytes<32>;

witness local_secret_key(): Bytes<32>;

export circuit initialize(): [] {
  const pk = publicKey(local_secret_key()).bytes;
  owner.write(disclose(pk));
}

export circuit increment(): [] {
  const caller = publicKey(local_secret_key()).bytes;
  assert(disclose(caller) == owner.read(), "Not authorized");
  counter.increment(1);
}
Enter fullscreen mode Exit fullscreen mode

Things the skills teach your AI that it wouldn't otherwise know:

  • export circuit not function, circuits declare constraints, they don't execute
  • disclose() is required when moving witness data to the public ledger, the compiler rejects code without it
  • Counter uses .increment() and .read(), not .value()
  • No recursion, no unbounded loops, circuits must compile to a fixed-size constraint system

Step 5: Compile

compact build src/counter.compact src/managed/counter
Enter fullscreen mode Exit fullscreen mode

Success looks like:

Fetching public parameters for k=10 [====================] 192.38 KiB
  circuit "increment" (k=10, rows=29)
Overall progress [====================] 1/1
Enter fullscreen mode Exit fullscreen mode

Step 6: Deploy

With the midnight-api skill loaded, your AI generates the correct facade 4.x pattern:

import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
import { CounterContract } from './managed/counter/contract/index.js';

// facade 4.x, the old WalletFacade.init() hangs silently on standalone nodes
const wallet = new WalletFacade(shielded, unshielded, dust);
await wallet.start();

const contract = new CounterContract();
const deployed = await contract.deploy(providers, { privateCounter: 0 });
console.log('Deployed at:', deployed.deployTxData.public.contractAddress);
Enter fullscreen mode Exit fullscreen mode

The skills include the SDK compatibility matrix. Your AI knows that wallet-sdk-facade@2.x uses WalletFacade.init() which hangs silently on standalone nodes, and that 4.x switched to new WalletFacade(...) + .start().


Step 7: Run a pre-flight check

Before you spend hours debugging, run:

npx midnight-doctor
Enter fullscreen mode Exit fullscreen mode

It reads your package.json, running Docker containers, and config files, then cross-references them against a known compatibility matrix. The midnight-api skill documents the most common silent failures:

Symptom Root cause
waitForSyncedState() hangs forever facade 2.x + standalone node mismatch
Transactions silently fail Duplicate @midnight-ntwrk/ledger-v7 in node_modules
Indexer crash-loops Missing subscription: block in indexer.yml

What the skills know that your AI doesn't

The skills pull from the official Midnight docs plus 19 community articles from the Midnight Aliit Fellowship, builders documenting real production failures.

Mental model shifts:

  • Circuits declare constraints, they don't execute. assert is a constraint declaration, not a runtime guard, if the condition is false, the proof can't be generated.
  • disclose() is a compile-time annotation, not encryption. The compiler tracks witness data through arithmetic and rejects undeclared disclosures.
  • Block limits are hard limits, not gas costs. BlockLimitExceeded means your transaction can't execute at all.

On-chain design patterns:

  • Flat maps over struct maps, reading a struct pulls every field into the circuit
  • Off-chain computation + Merkle root, the chain verifies, it doesn't compute
  • Minimal on-chain state, only what the chain needs to enforce

Common syntax gotchas:

  • counter.read() not counter.value()
  • Enum access uses . not ::, GameState.playing not GameState::playing
  • Witness functions have no body, declaration only
  • return inside for loops is not allowed, use fold

Install and start building

npx skills add https://github.com/mzf11125/midnight_agent_skills
Enter fullscreen mode Exit fullscreen mode

The skills are open source. If you hit a pattern that's missing, PRs are open.

Repo: https://github.com/mzf11125/midnight_agent_skills

Top comments (0)