CompactForge Docs
Everything you need to build, compile, deploy, and interact with Compact smart contracts on the Midnight Network.
Overview
- CI/CD Pipeline — GitHub Actions that compile Compact circuits on every push
- Proof Benchmarking — Per-circuit proving times in Neon Postgres
- Live Dashboard — Deployments, CI runs, charts — wired to a real database
- Deploy & Interact — One-click deployment and circuit invocation via 1AM wallet
- token_ledger — Reference Compact contract with 6 ZK circuits
Prerequisites
Install these before running locally:
Node.js >= 18
LTS recommended
npm >= 9
or pnpm/yarn
Git
for webhooks & CI
1AM Wallet
Chrome extension — required for Deploy/Interact
Neon Postgres
Free tier; provides DATABASE_URL
Compact CLI >= 0.20
For contract compilation in CI
Installation
1. Clone the repository
git clone https://github.com/sauravs296/CompactForge.git
cd CompactForge2. Environment variables
Create a .env file:
DATABASE_URL="postgresql://user:pass@host/db?sslmode=require"
GITHUB_WEBHOOK_SECRET="your_secret"
GITHUB_TOKEN="ghp_xxxxx"3. Install & setup database
npm install
npx prisma generate
npx prisma db push4. Start dev server
npm run dev
# → http://localhost:3000package.json.Architecture
CompactForge/
├── src/
│ ├── app/
│ │ ├── (marketing)/page.tsx ← Landing page
│ │ ├── docs/page.tsx ← This docs page
│ │ ├── dashboard/
│ │ │ ├── page.tsx ← Overview (KPIs + charts)
│ │ │ ├── contracts/page.tsx ← Contract details
│ │ │ ├── deployments/page.tsx ← Deployment history
│ │ │ ├── benchmarks/page.tsx ← Proof benchmark table
│ │ │ ├── ci-runs/page.tsx ← CI run log
│ │ │ └── interact/page.tsx ← Deploy + Interact panel
│ │ └── api/
│ │ ├── contracts/token_ledger/
│ │ │ ├── contract/ ← Metadata endpoint
│ │ │ ├── zkir/[circuit]/ ← Serves .bzkir ZK IR
│ │ │ └── keys/[filename]/ ← Serves .prover/.verifier
│ │ ├── deployments/ ← CRUD for deployments
│ │ ├── benchmarks/ ← Benchmark data
│ │ └── ci-runs/ ← Webhook receiver
│ └── components/
│ ├── DeployButton.tsx ← One-click deployment
│ ├── InteractPanel.tsx ← 6-circuit interaction
│ └── WalletConnectButton.tsx ← 1AM wallet connect
├── contracts/token_ledger/
│ ├── token_ledger.compact ← Compact source
│ └── build/token_ledger/
│ ├── contract/index.js ← Compiled JS module
│ ├── keys/*.prover ← ZK prover keys (2-10 MB)
│ ├── keys/*.verifier ← ZK verifier keys (~2 KB)
│ └── zkir/*.bzkir ← ZK intermediate repr
├── prisma/schema.prisma ← DB schema
└── .github/workflows/ ← 3 CI/CD workflowsThe Contract
token_ledger.compact — privacy-preserving token ledger demonstrating the full Midnight ZK model:
Public Ledger State
balances, totalSupply, ownerCount, admin, paused — all on-chain
Private Witnesses
localSecretKey() — caller key, never touches the chain
disclose() calls
Every ledger write wrapped in disclose() as Compact requires
ZK-safe arithmetic
Explicit as Uint<64> casts to prevent circuit overflow
assert() guards
Access control at compile-time
Circuit breaker
pause() / unpause() — admin-only emergency freeze
Ledger declaration
pragma language_version >= 0.20;
import CompactStandardLibrary;
export ledger balances: Map<Bytes<32>, Uint<64>>;
export ledger totalSupply: Uint<64>;
export ledger ownerCount: Uint<32>;
export ledger admin: Bytes<32>;
export ledger paused: Boolean;
witness localSecretKey(): Bytes<32>;
constructor(adminAddress: Bytes<32>) {
admin = disclose(adminAddress);
totalSupply = disclose(0 as Uint<64>);
ownerCount = disclose(0 as Uint<32>);
paused = disclose(false);
}Circuits Reference
6 circuits, each with its own ZK prover/verifier keypair:
deposit(amount: Uint<64>): []PublicAdds tokens to caller's own balance via localSecretKey() identity.
| Arg | Type | Note |
|---|---|---|
| amount | Uint<64> | Must be > 0 |
transfer(recipient: Bytes<32>, amount: Uint<64>): []PublicZK-private: proves sender has balance without revealing identity.
| Arg | Type | Note |
|---|---|---|
| recipient | Bytes<32> | 32-byte public address |
| amount | Uint<64> | <= sender balance |
mint(recipient: Bytes<32>, amount: Uint<64>): []Admin onlyCreates tokens assigned to recipient. Admin key checked in circuit.
| Arg | Type | Note |
|---|---|---|
| recipient | Bytes<32> | Address receiving tokens |
| amount | Uint<64> | Must be > 0 |
burn(amount: Uint<64>): []PublicDestroys caller's tokens. Reduces totalSupply permanently.
| Arg | Type | Note |
|---|---|---|
| amount | Uint<64> | <= caller balance |
pause(): []Admin onlySets paused=true. All other circuits assert(!paused) and revert.
No arguments required.
unpause(): []Admin onlyClears paused flag, resuming operations.
No arguments required.
CI/CD Pipeline
3 GitHub Actions workflows on every push to main:
.github/workflows/contracts.ymlCompiles all Compact circuits. POSTs benchmark data to /api/benchmarks on success.
.github/workflows/frontend.ymlRuns next build — fails fast on TypeScript errors.
.github/workflows/typecheck.ymlRuns tsc --noEmit for strict type-checking.
GitHub Webhook
# GitHub repo → Settings → Webhooks
Payload URL: https://your-domain.com/api/ci-runs
Content type: application/json
Secret: <GITHUB_WEBHOOK_SECRET from .env>
Events: pushDashboard
KPI overview: contracts, deployments, avg proof time, CI success rate
Contract details, circuits, benchmark data
History with contract address + 1AM Explorer links
Per-circuit proving times with commit verify links
GitHub Actions run log
Deploy and call any of the 6 circuits
Deploy & Interact
Deployment flow
- 11AM Wallet opens permission prompt — click Approve
- 2App fetches your wallet address and shielded keys in parallel
- 3createUnprovenDeployTx builds the unproven deployment transaction
- 41AM ProofStation generates the ZK proof (downloads keys from /api/contracts/token_ledger/keys/)
- 5balanceUnsealedTransaction adds dust fees via the 1AM server wallet (you pay 0 NIGHT)
- 6submitTransaction broadcasts to Midnight Preprod network
- 7Contract address shown in toast — copy for Interact panel
- 8Deployment saved to database automatically
Circuit arguments
| Circuit | Args |
|---|---|
| deposit | [BigInt(amount)] |
| burn | [BigInt(amount)] |
| transfer | [parseAddressToBytes32(recipient), BigInt(amount)] |
| mint | [parseAddressToBytes32(recipient), BigInt(amount)] |
| pause | [] |
| unpause | [] |
Benchmarks
curl -X POST https://your-domain/api/benchmarks \
-H "Content-Type: application/json" \
-d '{
"commitSha": "abc1234",
"benchmarks": [
{ "circuit": "deposit", "provingMs": 1340 },
{ "circuit": "transfer", "provingMs": 1810 },
{ "circuit": "mint", "provingMs": 1890 },
{ "circuit": "burn", "provingMs": 1120 },
{ "circuit": "pause", "provingMs": 750 },
{ "circuit": "unpause", "provingMs": 750 }
]
}'API Reference
/api/deploymentsList all deployments
/api/deploymentsRecord a deployment { contractAddress, txId, network }
/api/deployments/[id]Get single deployment
/api/benchmarksList benchmark records
/api/benchmarksRecord CI benchmark data { commitSha, benchmarks[] }
/api/ci-runsList CI run history
/api/ci-runsGitHub webhook receiver
/api/contracts/token_ledger/contractContract metadata
/api/contracts/token_ledger/zkir/[circuit]Streams .bzkir ZK IR file
/api/contracts/token_ledger/keys/[filename]Streams .prover or .verifier key
Why Local Only?
Deploy & Interact cannot run on Vercel or any hosted deployment.
- 1.ZK proving keys read from disk —
.proverand.verifierfiles (2-10 MB each) are served via API routes fromcontracts/token_ledger/build/token_ledger/keys/. Vercel serverless functions have size limits that prevent serving large binaries this way. - 2.1AM ProofStation calls back to your server — the cloud ProofStation tries to download key files from the URL you configured. It cannot reach
localhost:3000. The app must be publicly accessible — OR the custom inline provider we use forces key fetching in the browser extension (works only locally).
Production workaround
- 1Host .prover / .verifier key files on CDN (Cloudflare R2, AWS S3) with CORS: *
- 2Use FetchZkConfigProvider with the CDN URL as base
- 3The 1AM ProofStation can fetch keys from the public CDN
- 4The Next.js frontend can run on Vercel — only key files need a CDN
npm install && npm run dev, and everything works end-to-end.