Skip to main content

Development Setup

Set up your development environment for building ZK applications with ZK-Kit.

Project Structure

Recommended folder structure for ZK-Kit projects:

my-zk-app/
├── src/
│ ├── circuits/ # Circom circuits (if using)
│ ├── contracts/ # Smart contracts (if using)
│ ├── lib/
│ │ ├── auth.ts # Authentication logic
│ │ ├── proofs.ts # Proof generation
│ │ └── trees.ts # Merkle tree management
│ ├── utils/
│ │ └── crypto.ts # Cryptographic utilities
│ └── index.ts # Main entry point
├── test/
│ ├── auth.test.ts
│ └── proofs.test.ts
├── examples/
│ └── basic-proof.ts
├── package.json
├── tsconfig.json
├── .env.example
└── README.md

Initial Setup

1. Create Project

mkdir my-zk-app && cd my-zk-app
npm init -y

2. Install Dependencies

# ZK-Kit packages
npm install @zk-kit/imt @zk-kit/eddsa-poseidon
# + peer dependencies (check npm pages for each package)

# Development dependencies
npm install --save-dev typescript @types/node ts-node nodemon

# Testing
npm install --save-dev jest @types/jest ts-jest

# Linting
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier

3. TypeScript Configuration

Create tsconfig.json:

{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test"]
}

4. Package Scripts

Update package.json:

{
"scripts": {
"build": "tsc",
"dev": "nodemon --exec ts-node src/index.ts",
"start": "node dist/index.js",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src --ext .ts",
"format": "prettier --write \"src/**/*.ts\"",
"clean": "rm -rf dist"
}
}

Testing Setup

Jest Configuration

Create jest.config.js:

module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
}

Example Test

Create test/proofs.test.ts:

import { IMT } from "@zk-kit/imt"
import { poseidon2 } from "poseidon-lite"

describe('Proof Generation', () => {
let tree: IMT

beforeEach(() => {
tree = new IMT(poseidon2, 16, 0, 2)
})

test('should create valid proof', () => {
const leaf = BigInt(1)
const index = tree.insert(leaf)
const proof = tree.createProof(index)

expect(tree.verifyProof(proof)).toBe(true)
})

test('should reject invalid proof', () => {
tree.insert(BigInt(1))
const proof = tree.createProof(0)
proof.leaf = BigInt(999) // Tamper with proof

expect(tree.verifyProof(proof)).toBe(false)
})
})

Linting Setup

ESLint Configuration

Create .eslintrc.json:

{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"argsIgnorePattern": "^_"
}]
}
}

Prettier Configuration

Create .prettierrc:

{
"semi": false,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}

Environment Variables

Create .env.example:

# Application
NODE_ENV=development
PORT=3000

# Blockchain (if using)
RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
PRIVATE_KEY=your_private_key_here

# ZK Configuration
TREE_DEPTH=20
ZERO_VALUE=0

# Database (if using)
DATABASE_URL=postgresql://user:pass@localhost:5432/zkapp

Create .env:

cp .env.example .env
# Edit .env with your values

Add to .gitignore:

.env
node_modules/
dist/
coverage/
*.log

Development Workflow

1. Create a Feature

// src/lib/auth.ts
import { IMT } from "@zk-kit/imt"
import { poseidon2 } from "poseidon-lite"

export class AuthSystem {
private tree: IMT

constructor(depth: number = 16) {
this.tree = new IMT(poseidon2, depth, 0, 2)
}

register(commitment: bigint): number {
return this.tree.insert(commitment)
}

createProof(index: number) {
return this.tree.createProof(index)
}

verify(proof: any): boolean {
return this.tree.verifyProof(proof)
}

get root() {
return this.tree.root
}
}

2. Write Tests

// test/auth.test.ts
import { AuthSystem } from '../src/lib/auth'
import { poseidon2 } from 'poseidon-lite'

describe('AuthSystem', () => {
let auth: AuthSystem

beforeEach(() => {
auth = new AuthSystem()
})

test('should register users', () => {
const commitment = poseidon2([BigInt(1)])
const index = auth.register(commitment)
expect(index).toBe(0)
})

test('should verify proofs', () => {
const commitment = poseidon2([BigInt(1)])
const index = auth.register(commitment)
const proof = auth.createProof(index)
expect(auth.verify(proof)).toBe(true)
})
})

3. Run Development Server

npm run dev

4. Run Tests

npm test

Debugging

VS Code Configuration

Create .vscode/launch.json:

{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug TypeScript",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"runtimeArgs": ["-r", "ts-node/register"]
},
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--no-cache"],
"console": "integratedTerminal"
}
]
}

Debug Logging

// src/utils/logger.ts
export function debug(label: string, data: any) {
if (process.env.DEBUG === 'true') {
console.log(`[${label}]`, data)
}
}

// Usage
import { debug } from './utils/logger'

const tree = new IMT(poseidon2, 16, 0, 2)
debug('tree-root', tree.root.toString())

Performance Monitoring

// src/utils/benchmark.ts
export function benchmark<T>(
name: string,
fn: () => T
): T {
const start = performance.now()
const result = fn()
const end = performance.now()
console.log(`${name}: ${(end - start).toFixed(2)}ms`)
return result
}

// Usage
import { benchmark } from './utils/benchmark'

benchmark('proof-generation', () => {
const proof = tree.createProof(0)
return proof
})

Git Workflow

.gitignore

# Dependencies
node_modules/

# Build
dist/
build/

# Environment
.env
.env.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# Logs
logs/
*.log

# Testing
coverage/
.nyc_output/

# OS
.DS_Store
Thumbs.db

Commit Hooks

Install husky:

npm install --save-dev husky lint-staged
npx husky install

Add to package.json:

{
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
]
}
}

Create hook:

npx husky add .husky/pre-commit "npx lint-staged"

CI/CD Setup

GitHub Actions

Create .github/workflows/test.yml:

name: Test

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Run linter
run: npm run lint

- name: Run tests
run: npm test

- name: Build
run: npm run build

Docker Setup

Dockerfile

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

CMD ["node", "dist/index.js"]

docker-compose.yml

version: '3.8'

services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
volumes:
- ./data:/app/data

Best Practices

1. Type Safety

// Define types for your domain
export interface UserCommitment {
commitment: bigint
index: number
timestamp: number
}

export interface ProofData {
proof: any
nullifier: bigint
root: bigint
}

2. Error Handling

export class AuthError extends Error {
constructor(message: string) {
super(message)
this.name = 'AuthError'
}
}

// Usage
if (!tree.verifyProof(proof)) {
throw new AuthError('Invalid proof provided')
}

3. Configuration Management

// src/config.ts
export const config = {
tree: {
depth: parseInt(process.env.TREE_DEPTH || '20'),
zeroValue: parseInt(process.env.ZERO_VALUE || '0')
},
server: {
port: parseInt(process.env.PORT || '3000')
}
}

Next Steps

Resources