example-leaderboard-dapp
Build a privacy-preserving arcade leaderboard DApp on Midnight Network — players submit click-challenge scores with anonymous, public, or custom display names, and prove entry ownership via zero-knowledge proofs without revealing secret keys. Covers leaderboard.compact (Map, Counter, witnesses, persistentHash owner commitments), Next.js frontend, 1AM wallet integration, low-level deploy/call, indexer reads without wallet, and ZK asset hosting. Use for gamified on-chain rankings, pseudonymous competitions, score attestations, or teaching disclose()/witness patterns. Triggers: leaderboard dApp, privacy leaderboard, submit score, verify ownership, anonymous player name, ZK proof of ownership, persistentHash identity, arcade game on Midnight. Also use when extending locker-dapp or payment-dapp wallet/provider patterns to social or gaming flows.
How do I install this agent skill?
npx skills add https://github.com/kali-decoder/midnight-skills --skill example-leaderboard-dappIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill is a legitimate development template for building a privacy-preserving leaderboard application on the Midnight Network. It utilizes official ecosystem tools, standard cryptographic patterns, and established wallet integrations without any detected malicious behavior.
- Socketpass
No alerts
- Snykwarn
Risk: MEDIUM · 2 issues
What does this agent skill do?
Midnight Network Leaderboard DApp
A privacy-preserving leaderboard where players submit scores on-chain with three display modes and prove they own an entry using verifyOwnership — a ZK circuit that checks ownerCommitment(secretKey) matches the stored ownerHash without revealing the secret.
Runnable template: Copy templates/leaderboard-dapp/ for a complete Next.js project (contract + UI). Run npm install && npm run compact && npm run sync:assets && npm run dev after installing the 1AM wallet.
What this skill produces:
contract/— Compact leaderboard + TypeScript witnesses + compile scriptsapp/leaderboard/— Next.js client UI (click game, privacy mode picker, leaderboard table)lib/midnight.ts— wallet session + patched indexer provider (copy fromreferences/midnight-session.mdortemplates/leaderboard-dapp/lib/midnight.ts)lib/leaderboard.ts— deploy,submitScore,verifyOwnership, ledger decodelib/display-name.ts— decode anonymous hash bytes to generated names ("Crimson Tiger")public/zk/leaderboard/— ZK proving assets synced from contract build
Shared references (canonical provider + troubleshooting — do not duplicate in prompts):
references/midnight-session.md—createConnectedSession, indexer patch, deploy/call helpersreferences/gotchas.md— preprod deploy hangs, GraphQLoffset: null, ZK asset pathsreferences/versions.json— pinned@midnight-ntwrk/*versions
Primary references:
example-locker-dapp//templates/locker-dapp/— Next.js + 1AM pattern, low-level deploy/callexample-payment-dapp/— provider wiring, indexer pollingcompact/—disclose(), witnesses,persistentHash,Map,Counter,assertsecurity/— what is public on-chain vs private in witness dataindexer/— read contract state without wallet connection
Key architecture notes:
scoresis a publicMap<Uint<64>, ScoreEntry>— readable from the indexer by anyonelocalSecretKeywitness returns a 32-byte secret from private state; never disclosed on-chainownerCommitment(sk)usespersistentHashwith domain separator"leaderboard:owner:"useCustomNameboolean selects witness-fed name vs hash-based anonymous display nameverifyOwnershipis a proof-only circuit — no ledger writes; use for "prove this is my score"- Persist secret key in
localStorageso ownership proofs work after page refresh - Use
createUnprovenDeployTx+submitTxAsync— notdeployContract()(hangs on preprod) - Wrap
indexerPublicDataProviderwith patchedqueryContractState(GraphQLoffset: nullbug) - Leaderboard table can load from indexer without wallet — only submit/verify need connection
Workflow
When helping the user, follow this sequence:
- Contract —
leaderboard.compactcompile + witnesses (localSecretKey,getCustomName) - Providers —
createConnectedSession(fromreferences/midnight-session.md) - Deploy — low-level deploy, persist private state + contract address
- Read state — indexer GraphQL →
ContractState.deserialize→ledger()→ decode entries - Submit score —
submitScore(score, useCustomName)with optionalsetCustomNamebeforehand - Verify ownership —
verifyOwnership(entryId)for prize claims or "yours" badges - UI — click game, privacy mode selector, leaderboard table with prove button
1) Project Structure
leaderboard-dapp/
├── package.json
├── next.config.mjs
├── lib/
│ ├── isomorphic-ws-fix.mjs
│ ├── midnight.ts # session, patched provider, hex helpers
│ ├── leaderboard.ts # deploy, submitScore, verifyOwnership, decode
│ └── display-name.ts # anonymous name generator
├── app/
│ ├── layout.tsx
│ └── leaderboard/
│ └── LeaderboardClient.tsx # game + wallet + leaderboard UI
├── contract/
│ ├── package.json
│ └── src/
│ ├── leaderboard.compact
│ ├── witnesses.ts
│ ├── index.ts
│ └── managed/leaderboard/ # compiler output (gitignored)
├── scripts/
│ └── sync-zk-assets.mjs # → public/zk/leaderboard/
└── public/zk/leaderboard/ # keys + zkir (gitignored until sync)
2) Compact Contract
contract/src/leaderboard.compact:
struct ScoreEntry {
score: Uint<64>,
displayName: Bytes<32>,
ownerHash: Bytes<32>
}
export ledger scores: Map<Uint<64>, ScoreEntry>;
export ledger nextId: Counter;
witness localSecretKey(): Bytes<32>;
witness getCustomName(): Bytes<32>;
export circuit ownerCommitment(sk: Bytes<32>): Bytes<32> {
return persistentHash<Vector<2, Bytes<32>>>([pad(32, "leaderboard:owner:"), sk]);
}
export circuit submitScore(score: Uint<64>, useCustomName: Boolean): [] { /* ... */ }
export circuit verifyOwnership(targetEntryId: Uint<64>): [] { /* ... */ }
Privacy design:
- Anonymous mode (
useCustomName = false):displayName = persistentHash(sk)— UI decodes to generated name - Public / Custom mode (
useCustomName = true):displayName = getCustomName()witness — app feeds address or user name - Ownership:
ownerHash = ownerCommitment(sk)stored on every entry;verifyOwnershipre-derives and compares
Compile:
cd contract && npm run compact && cd ..
npm run sync:assets
3) Witnesses
contract/src/witnesses.ts:
export type LeaderboardPrivateState = { secretKey: Uint8Array };
export const witnesses = {
localSecretKey: (ctx) => [ctx.privateState, ctx.privateState.secretKey] as const,
getCustomName: (ctx) => [ctx.privateState, customName] as const,
};
export const setCustomName = (name: string) => { /* encode to Bytes<32> */ };
Call setCustomName(name) in TypeScript before submitScore when using public or custom display modes.
4) TypeScript Integration
lib/leaderboard.ts mirrors the locker-dapp pattern:
| Function | Purpose |
|---|---|
getOrCreateSecretKey() | Persist 32-byte secret in localStorage |
deployLeaderboard(session) | createUnprovenDeployTx + submitTxAsync |
submitScore(session, addr, score, customName?) | submitCallTxAsync → submitScore circuit |
verifyOwnership(session, addr, entryId) | submitCallTxAsync → verifyOwnership circuit |
fetchLeaderboardState(queryUrl, addr) | Indexer poll + decodeLeaderboardState |
ZK asset path: /zk/leaderboard (synced to public/zk/leaderboard/).
5) Browser UI
LeaderboardClient.tsx flow:
- Connect —
detectWallet()→wallet.connect('preprod')→createConnectedSession(api, ZK_PATH) - Deploy / Join — deploy new contract or paste existing 64-char hex address
- Play — 10-second click challenge
- Submit — pick Anonymous / Public / Custom, then
submitScore - Leaderboard — auto-refresh from indexer every 15s (no wallet required to read)
- Prove —
verifyOwnership(entryId)marks entry as yours in UI
Indexer-only reads mean spectators can watch the leaderboard without connecting a wallet.
6) Prerequisites
| Component | Version |
|---|---|
| Compact compiler | 0.31.0 |
| Compact runtime | 0.16.0 |
| Ledger | 8.0.3 |
| midnight-js | 4.0.4 |
| proof server | 8.0.3 |
docker run -d -p 6300:6300 midnightntwrk/proof-server:8.0.3 -- \
midnight-proof-server --network preprod
Wallet: 1AM extension on Preprod, proof server http://localhost:6300, tNIGHT + tDUST from faucet.
7) Compatibility Matrix
Pin versions from references/versions.json. Cross-check docs.midnight.network support matrix before upgrading.
8) Production Notes
- Deploy frontend to Vercel/Netlify; set
public/zk/leaderboardassets in build step (npm run sync:assets) - Store default contract address in env or
localStoragefor returning players verifyOwnershipdoes not mutate ledger — safe for repeated "prove mine" UX- For mainnet, update network ID, indexer URLs, and proof server config via wallet
getConfiguration()
Quick Commands
cd templates/leaderboard-dapp
npm install
npm run compact
npm run sync:assets
npm run dev
Open http://localhost:3000 → Connect 1AM → Deploy New → play → submit → refresh leaderboard.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/kali-decoder/midnight-skills/example-leaderboard-dapp">View example-leaderboard-dapp on skillZs</a>