neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A private voting/polling contract for Midnight, built for the Moonlight Challenges — Level 1: New Moon, Level 2: First Crescent, and Level 3: Half Moon.
Live demo: shadowpoll-frontend.vercel.app
**Contract (network: Preview, not Preprod — Preprod is blocked by a faucet bug, see why):** 2daaffd761b9695e4ddede415d83b7dade6b999ea0da825e60ba7c9c6b55d3a7
Proposal: PROPOSAL.md
Demo video: Watch on Loom
Chosen idea: Private Voting — anonymous ballots with publicly verifiable tallies (see Level 3)README 전문 보기 ↗A private voting/polling contract for Midnight, built for the Moonlight Challenges — Level 1: New Moon, Level 2: First Crescent, and Level 3: Half Moon.
Live demo: shadowpoll-frontend.vercel.app
Contract (network: Preview, not Preprod — Preprod is blocked by a faucet bug, see why): 2daaffd761b9695e4ddede415d83b7dade6b999ea0da825e60ba7c9c6b55d3a7
Proposal: PROPOSAL.md
Demo video: Watch on Loom
Chosen idea: Private Voting — anonymous ballots with publicly verifiable tallies (see Level 3)
ShadowPoll is a minimal private polling primitive for Midnight: anyone can open a yes/no poll, and anyone can cast a vote that gets folded into a public running tally — without ever revealing who voted or how they voted. Today's DAO and community polling tools force a trade-off between transparency (public tallies, but every vote is doxxed and open to social pressure or vote-buying) and privacy (secret ballots, but no way to verify the count wasn't tampered with). ShadowPoll resolves that by keeping the ballot private as a Compact witness while the tally itself lives in public ledger state, so anyone can audit the result on-chain while no one — not even the contract — ever learns an individual's choice. The long-term idea is to grow this into a lightweight polling widget that DAOs, communities, and workplaces can embed to run quick, tamper-evident, coercion-resistant votes.
This is the core privacy pattern the contract demonstrates:
| Public ledger state | Private witness | |
|---|---|---|
| What | question, yesVotes, noVotes, voted (a set of nullifiers), creatorNullifier, closed | mySecretId (a persistent per-identity secret), myChoice (this vote's yes/no) |
| Who can see it | Anyone reading the chain / indexer | Only that identity's own wallet — resolved locally, never transmitted |
| Why | The whole point of a poll is a publicly verifiable result | The whole point of privacy is that no one can link a person to a vote, or to the fact that they created the poll |
Concretely, in contract/src/shadowpoll.compact:
castVote() derives a nullifier — persistentHash(secretId) — from the voter's private mySecretId witness and disclose()s only that hash to the public voted set. This proves "this identity hasn't voted yet" without ever revealing the identity itself, since the hash can't be reversed back to the secret.myChoice witness (yes/no) is disclose()d at the point it's used to decide which counter (yesVotes or noVotes) to increment. That disclosure is deliberate and unavoidable — the aggregate tally is the public output of a poll — but because it's never combined with the nullifier or any other identifying data in the same disclosure, the running totals are the only thing anyone can observe. Nothing on-chain ties a specific increment back to a specific voter.secretId), so even repeated identical votes never collide or leak linkage.closePoll() applies the identical technique to a second, separate role. The poll's creator commits a creatorNullifier at deploy time (the same mySecretId, hashed in its own domain via creatorNullifierFor so it can never collide with - or be linked to - that same identity's voter nullifier). Closing the poll means re-deriving that nullifier and proving it matches, without ever disclosing who the creator is. A creator who also votes leaves two unlinkable public traces, not one.This mirrors the general Compact pattern: keep everything in witness functions by default, and reach for disclose() only at the exact point where a value is deliberately meant to become public — never by accident.
Level 2 asks for "something proven without being shown." Here it is: connect a wallet, cast one vote, then try to vote again from that same wallet. The second attempt is rejected — the contract proves you already voted — but at no point does the UI, the transaction, or the public ledger state ever reveal what you voted or which on-chain identity you are. The only thing that changes publicly is one of two aggregate counters. Anyone watching the indexer sees a tally move and a nullifier hash appear in a set; no one, including whoever's running the poll, can work backwards from that to a person or a choice. See docs/demo-script.md for the exact walkthrough.
contract/ Compact contract, compiled circuits (managed/), and unit tests (vitest)
api/ Thin TypeScript API wrapping the compiled contract (deploy/join/castVote)
cli/ Deployment scripts for Preview/Preprod using a local proof server
frontend/ React/Vite web UI: live poll display + Lace-connected voting
.nvmrc)compact CLI), installed via:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
compact update
# 1. Install dependencies (installs every workspace: contract, api, cli, frontend)
npm install
# 2. Compile the Compact contract into circuits + keys (managed/)
npm run compact --workspace=contract
# 3. Run the contract's test suite
npm run test --workspace=contract
# 4. Build the TypeScript packages
npm run build --workspace=contract
npm run build --workspace=api
Toolchain set up, first Compact contract written and deployed to Preview.
npm run compact --workspace=contract runs compact compile src/shadowpoll.compact ./src/managed/shadowpoll and generates:
contract/src/managed/shadowpoll/
├── compiler/contract-info.json
├── contract/index.{js,d.ts} # generated TS bindings (Ledger type, Contract class, pureCircuits)
├── keys/castVote.{prover,verifier}
└── zkir/castVote.{zkir,bzkir}
Deployment uses a local proof server (rather than the ephemeral docker-managed one) for reliability:
# 1. Start a local proof server
cd cli
docker compose -f proof-server-local.yml up -d
# 2. Deploy (generates a fresh wallet, requests test tokens from the network
# faucet, registers DUST for fees, then deploys the contract)
npm run preview-direct # or: npm run preprod-direct
Each run logs progress to logs/<network>-direct/<timestamp>.log, culminating in the deployed contract address.
Deployed contract (Preview), original Level 1 version: e5facde142e36093a5430224340c8ebf7675ed90fdfdeb6be7183f895458d34d — independently queryable via the Preview indexer's contract(address: "...") GraphQL query. Superseded for Level 3 by a redeploy with closePoll added - see Level 3 for the current live contract address.
To re-use a specific wallet instead of generating a fresh one, set WALLET_SEED (or WALLET_MNEMONIC) in the environment before running the deploy script. Never use a seed that holds real funds — this script logs the seed and persists private state to disk.
Note on wallet sync:
waitForUnshieldedFunds/syncWalletwait for a full sync (shielded + unshielded + dust lanes), not just a non-zero balance. A wallet with a long prior transaction history can take several minutes to fully resync its DUST-lane merkle/witness state from a fresh local store; building a deploy transaction before that finishes produces a proof the chain rejects (1010: Invalid Transaction: Custom error: 170/InvalidDustSpendProof), even though the balance already looks ready. A wallet address with no prior history on a given network needs a genuinely long first-time sync (many minutes, multiple GB of memory) - seecli/src/wallet-utils.tsfor how that's made resilient to transient stalls.
Contract wired to a frontend UI, with Lace connected.
# 1. Compile the contract first if you haven't (frontend serves its circuit
# artifacts as static files, copied from contract/src/managed)
npm run compact --workspace=contract
# 2. Run the frontend
npm run dev --workspace=frontend
Live at shadowpoll-frontend.vercel.app. The landing page (/) is marketing copy; the actual product lives on separate routes:
/app or /app/:contractAddress — live poll display (frontend/src/hooks/usePollState.ts, reads the indexer directly, no wallet needed) plus Lace-connected voting. frontend/src/lib/wallet-bridge.ts bridges the injected dapp-connector API (e.g. Lace) to the WalletProvider/MidnightProvider interfaces @shadowpoll/api's ShadowPollAPI.castVote() expects - connect, get funds-aware state, call the castVote circuit, submit through the wallet, disconnect. Proving is delegated to the wallet itself by default (getProvingProvider, see frontend/src/lib/providers.ts), not a locally-run proof server, so this works for any visitor with a compatible wallet installed and no Docker setup. Set VITE_USE_LOCAL_PROOF_SERVER=true to instead prove against a proof server you run yourself./create — deploy a brand new poll from the browser (frontend/src/pages/CreatePoll.tsx), calling ShadowPollAPI.deploy() with the connected wallet, then redirecting to /app/:contractAddress for the poll just created. The creating wallet's identity becomes that poll's creator - the only one who'll ever see the closePoll control on it, without that role being visible to anyone else (see Public state vs. private witness).This is an npm-workspaces monorepo, and frontend/ depends on the sibling contract/ and api/ workspaces being built first - Vercel's (or Netlify's) framework auto-detection doesn't know that, and will also misread the top-level api/ folder as serverless functions if left on defaults. Configure these explicitly rather than relying on auto-detect:
| Setting | Value |
|---|---|
| Root Directory | frontend |
| Framework Preset | Vite |
| Install Command | echo skip-default-install (a no-op - the build command below does its own install) |
| Build Command | cd .. && npm install && npm run build --workspace=contract && npm run build --workspace=api && cd frontend && npm run build |
| Output Directory | dist |
No environment variables are required - frontend/src/lib/env.ts's defaults already point at the deployed contract below. See that file if you want to override the network or contract address instead.
Level 2 asks for Preprod specifically, and the frontend/CLI both support it (VITE_NETWORK=preprod, npm run preprod-direct) - but as of this writing, Preprod's faucet won't fund a deploying wallet, which blocks a fresh deploy there. This isn't a sync-time or memory issue (both of which came up and were fixed along the way, see cli/src/wallet-utils.ts) - it's that the SDK's faucet client (@midnight-ntwrk/testkit-js's FaucetClient) posts to the wrong endpoint with an outdated request shape. The real Preprod faucet frontend calls POST https://midnight-tmnight-preprod.nethermind.dev/api/request-tokens with a {address, captchaToken} body gated by Cloudflare Turnstile; the SDK instead posts {recipientAddress, amount} straight to the faucet's root URL, which Express's SPA fallback answers with a misleading 200 OK HTML page - so the deploy script logs "Faucet response: OK" and then waits forever, since no tokens were ever actually requested. Confirmed by replaying the real request manually:
$ curl -X POST https://midnight-tmnight-preprod.nethermind.dev/api/request-tokens \
-H "Content-Type: application/json" \
-d '{"address":"mn_addr_preprod1...","captchaToken":"x"}'
{"status":"error","message":"Captcha verification failed: invalid-input-response"}
A real captcha token only comes from a human completing the challenge in a browser, so this contract is deployed to Preview instead - originally the same address from Level 1, later redeployed for Level 3 with closePoll added (see Level 3) - with the frontend's Lace integration, circuit call, and privacy behavior otherwise identical to what Level 2 asks for on Preprod. Swapping networks once the faucet is fixed is a one-line env var change.
See The privacy claim, observable above, and docs/demo-script.md for the exact steps the demo video walks through: connect → cast a vote (circuit call) → attempt a second vote from the same wallet (rejected, without revealing why to any observer) → disconnect.
Watch on Loom - recorded by following docs/demo-script.md against the live deploy above: connect Lace, cast a vote (the castVote circuit call), and the observable privacy behavior.
A polished, production-grade dApp: tests, CI/CD, and a chosen problem from the Moonlight Challenges idea list.
ShadowPoll already is "Private Voting — anonymous ballots with publicly verifiable tallies," the first idea on the provided list - Levels 1 and 2 built exactly this, so Level 3 is about hardening it (tests, CI, honest documentation of what's real vs. in-progress) rather than starting a new project. See Product idea above for the full pitch, and PROPOSAL.md for the standalone proposal write-up (product, why Midnight, data model, and Mainnet-by-Level-6 feasibility).
Level 3 adds closePoll to the contract (see Public state vs. private witness above), which changes the ledger's on-chain schema, so it needed a fresh deploy rather than reusing the Level 1/2 address:
Deployed contract (network: Preview, not Preprod — see why): 2daaffd761b9695e4ddede415d83b7dade6b999ea0da825e60ba7c9c6b55d3a7 — independently queryable via the Preview indexer's contract(address: "...") GraphQL query. This is the address the live demo and frontend now point at. Preprod deploy is still blocked on the faucet client bug below; swapping networks once it's fixed is a one-line env var change.
npm run test --workspace=contract
11 tests in contract/src/test/shadowpoll.test.ts, each checking a specific privacy or correctness property rather than just exercising code paths:
closePoll from anyone other than the creator.github/workflows/ci.yml runs on every push/PR to main: installs the workspace, builds contract and api, runs the contract test suite, typechecks cli and frontend, and does a full production build of the frontend - so a broken build or a failing test is caught before merge, not discovered after deploy. Badge at the top of this README links to the latest run.
The Compact compiler itself isn't installed in the CI runner (it's a standalone toolchain, not an npm package) - the compiled circuit artifacts under contract/src/managed/ are committed to git precisely so CI (and anyone cloning the repo) doesn't need it just to build and test the TypeScript layers. Recompiling from .compact source still requires the toolchain locally, per Prerequisites.
Anyone with access to the Preview indexer or a block explorer - not just other players, literally anyone, with no special access - can query this contract. Here's exactly what that gets them:
An observer can learn:
voted nullifier set gains a new entry.voted set) vs. the sum of yesVotes + noVotes (these always match, which is itself a publicly verifiable integrity property - the tally can't be tampered with independently of real votes).closed, and the (opaque) creatorNullifier committed at deploy time.An observer cannot learn:
persistentHash(secretId)) is one-way; there's no computation that recovers secretId from it.myChoice is a private witness that only ever contributes to the two aggregate counters, never disclosed alongside anything identifying.creatorNullifier is exactly as opaque as any voter nullifier; closing the poll proves the caller knows the right secret without revealing it.creatorNullifierFor vs. nullifierFor) specifically so the two roles can never be linked to each other, even by someone who already knows both nullifiers.The one deliberate exception is unavoidable and stated plainly: the aggregate tally is intentionally public, because a poll whose result nobody can see isn't a poll. Privacy here means ballot privacy, not result privacy.
Watch on Loom - recorded by following docs/demo-script-level3.md: live public state, wallet connect, a private circuit call (vote), the observable privacy guarantee, and the creator-only closePoll circuit call.
In the interest of "production-grade" meaning honest, not just polished:
wallet-bridge.ts and providers.ts for what was actually wrong versus red herrings.Custom error: 170 / InvalidDustSpendProof), not just a timeout. The best working theory: a spend proof has to reference the wallet's view of a state root spanning all three lanes together, and shielded-lane scanning cost (checking every shielded output ever emitted on the whole chain) grows with the testnet's cumulative activity over calendar time, for every wallet, not just reused ones. cli/src/wallet-utils.ts documents this in detail and budgets accordingly (hours, not minutes) for anyone redeploying.