Complete guide for testing the ZERA platform locally, including smart contracts, web application, and storage services.
- Overview
- Prerequisites
- Quick Start
- Testing Smart Contracts
- Testing Web Application
- Testing Storage Service
- Integration Testing
- Troubleshooting
ZERA's local testing environment consists of three main components:
- Smart Contracts - Midnight Network contracts with ZK circuits
- Web Application - Next.js frontend with Prisma database
- Storage Service - Rust-based IPFS API
This guide covers how to test each component individually and as an integrated system.
Before running local tests, ensure you have:
- ✅ Node.js >= 22.0.0
- ✅ Bun package manager
- ✅ Docker and Docker Compose
- ✅ Compact Compiler installed and in PATH
- ✅ Cargo/Rust >= 1.75.0 (for storage service)
- ✅ PostgreSQL (via Docker or local installation)
- ✅ Pinata JWT Token (for IPFS testing)
# Check Node.js version
node --version # Should be >= 22.0.0
# Check Bun
bun --version
# Check Docker
docker --version
docker compose version
# Check Compact compiler
compact --version
# Check Rust/Cargo
cargo --version # Should be >= 1.75.0# From project root
bun install# Start Docker services (Midnight node, indexer, proof server, PostgreSQL)
bun run services:up
# Compile contracts and setup database
bun run project:prepare# Run smart contract tests
bun run test1. Start Local Midnight Network:
# From project root
bun run services:upThis starts:
- Midnight Node (port 9944) - Local blockchain
- Indexer (port 8088) - Blockchain data indexer
- Proof Server (port 6300) - ZK proof generation
- PostgreSQL (port 5432) - Database for web app
2. Verify Services are Running:
# Check Docker containers
docker compose ps
# Expected output:
# NAME IMAGE STATUS
# zera-indexer-1 midnightntwrk/indexer-standalone:4.0.0 Up (healthy)
# zera-node-1 midnightntwrk/midnight-node:0.22.3 Up (healthy)
# zera-proof-server-1 midnightntwrk/proof-server:8.0.3 Up
# zera-db postgres:16 Up3. Compile Smart Contracts:
# Compile Compact contracts
bun run compact:compile
# Build TypeScript bindings
bun --filter @zera/contracts buildRun Full Test Suite:
# From project root
bun run test
# Or from contracts directory
cd contracts
bun run testTest Coverage:
The test suite (contracts/test/hw.test.ts) includes:
- ✅ Contract deployment
- ✅ Asset registration
- ✅ Asset retrieval
- ✅ Asset existence checks
- ✅ Ownership assignment
- ✅ Ownership transfer
- ✅ Asset verification
- ✅ Ownership verification
- ✅ Error handling (non-existent assets, invalid owners)
- ✅ Duplicate prevention
Expected Output:
✓ Deploys the contract (5234ms)
✓ Registers a new asset (3421ms)
✓ Retrieves registered asset (2156ms)
✓ Checks asset existence (1987ms)
✓ Assigns ownership to asset (3102ms)
✓ Registers second asset (3298ms)
✓ Verifies asset authenticity (2543ms)
✓ Transfers ownership to new owner (3187ms)
✓ Verifies new ownership (2234ms)
✓ Fails when trying to assign ownership to non-existent asset (1876ms)
✓ Fails when trying to get non-existent asset (1654ms)
✓ Verifies that invalid owner cannot claim ownership (2109ms)
Test Files 1 passed (1)
Tests 12 passed (12)
Duration: ~35s
1. Deploy Contract Locally:
# From project root
bun run deploy2. Interact with Deployed Contract:
// Example: Register an asset
import { submitCallTx } from '@zera/contracts';
import { providers, compiledContract, contractAddress } from './setup';
const assetHash = new Uint8Array(32).fill(1);
const metadataHash = new Uint8Array(32).fill(2);
const timestamp = BigInt(Date.now());
await submitCallTx(providers, {
compiledContract,
contractAddress,
privateStateId,
circuitId: 'registerAsset',
args: [assetHash, metadataHash, timestamp]
});Enable Verbose Logging:
# Set log level
export RUST_LOG=debug
# Run tests with detailed output
bun run testCheck Service Logs:
# View all service logs
docker compose logs
# View specific service
docker compose logs node
docker compose logs indexer
docker compose logs proof-server# Stop all services
bun run services:down
# Clean build artifacts
bun run clean1. Ensure Services are Running:
# Start Docker services
bun run services:up2. Setup Database:
# Generate Prisma client and push schema
bun run db:setup3. Copy Contract Artifacts:
# Copy compiled contract artifacts to web/public
bun run copy-artifacts4. Configure Environment:
# Copy environment template
cd web
cp .env.example .env
# Edit .env with your configurationRequired Environment Variables:
# Database
DATABASE_URL="postgresql://postgres:zera@localhost:5432/zera"
# Midnight Network (local)
NEXT_PUBLIC_INDEXER_URL="http://localhost:8088"
NEXT_PUBLIC_PROOF_SERVER_URL="http://localhost:6300"
NEXT_PUBLIC_NODE_URL="ws://localhost:9944"
# IPFS Storage Service
NEXT_PUBLIC_IPFS_API_URL="http://localhost:8080"
# API
NEXT_PUBLIC_API_URL="http://localhost:3000"Start Development Server:
# From project root
bun run dev:web
# Or from web directory
cd web
bun run devAccess Application:
Open http://localhost:3000 in your browser.
Steps:
- Navigate to
/create-asset - Fill in asset details:
- Asset name
- Description
- Upload image/file
- Click "Register Asset"
- Wait for transaction confirmation
- Verify asset appears in dashboard
Expected Behavior:
- File uploads to IPFS via storage service
- Asset registers on Midnight blockchain
- Asset appears in database
- Asset visible in dashboard
Steps:
- Navigate to
/assets/[id] - Click "Verify Asset"
- Check verification status
Expected Behavior:
- ZK proof generated
- Verification circuit executes
- Verification status displayed
Steps:
- Navigate to owned asset
- Click "Transfer Ownership"
- Enter new owner's public key
- Confirm transfer
- Wait for transaction
Expected Behavior:
- Ownership transfers on-chain
- New owner can verify ownership
- Previous owner loses access
Access Prisma Studio:
# From web directory
npx prisma studioThis opens a GUI at http://localhost:5555 for:
- Viewing database records
- Manually editing data
- Testing queries
Reset Database:
# From web directory
bun run prisma:db-push --force-resetHealth Check:
curl http://localhost:3000/api/healthGet All Assets:
curl http://localhost:3000/api/assetsGet Asset by ID:
curl http://localhost:3000/api/assets/1Get Assets by Owner:
curl http://localhost:3000/api/assets/owner/[ownerAddress]Verify Asset:
curl -X POST http://localhost:3000/api/assets/[id]/verify \
-H "Content-Type: application/json" \
-d '{"publicKey": "0x..."}'Transfer Asset:
curl -X POST http://localhost:3000/api/assets/[id]/transfer \
-H "Content-Type: application/json" \
-d '{"newOwnerPublicKey": "0x..."}'1. Navigate to Storage Directory:
cd storage2. Configure Environment:
# Copy environment template
cp .env.example .env
# Edit .env with your Pinata JWTRequired Environment Variables:
# Pinata API JWT Token (required)
PINATA_JWT=your_jwt_token_here
# Server Configuration (optional)
PORT=8080
HOST=0.0.0.0
RUST_LOG=infoGet Pinata JWT Token:
- Sign up at Pinata Cloud
- Navigate to API Keys section
- Create new API key with pinning permissions
- Copy JWT token to
.env
3. Build Service:
# Build in release mode
cargo build --releaseStart Service:
# From storage directory
cargo run --release
# Or from project root
bun run storage:devExpected Output:
🚀 ZERA IPFS API Server
📡 Listening on: http://0.0.0.0:8080
🔗 Endpoints:
GET /health
POST /upload
GET /fetch/:cid
GET /verify/:cid
curl http://localhost:8080/health
# Expected response:
# {"status":"healthy","version":"2.0.0"}# Upload public file
curl -X POST http://localhost:8080/upload \
-F "file=@test-image.png"
# Expected response:
# {
# "success": true,
# "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
# "gateway": "https://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.ipfs.w3s.link",
# "is_private": false
# }# Upload private file
curl -X POST http://localhost:8080/upload?private=true \
-F "file=@document.pdf"curl http://localhost:8080/verify/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
# Expected response:
# {"success": true, "reachable": true}curl http://localhost:8080/fetch/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
# Expected response:
# {"success": true, "path": "downloads/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"}
# File saved to: storage/downloads/{cid}// Upload file
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const response = await fetch('http://localhost:8080/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('CID:', result.cid);
console.log('Gateway:', result.gateway);
// Verify CID
const verifyResponse = await fetch(
`http://localhost:8080/verify/${result.cid}`
);
const verifyResult = await verifyResponse.json();
console.log('Reachable:', verifyResult.reachable);# From storage directory
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_nameTest the complete flow from asset creation to ownership transfer:
1. Start All Services:
# From project root
bun run start:allThis command:
- Starts Docker services (Midnight network + PostgreSQL)
- Compiles smart contracts
- Deploys contracts locally
- Starts web application (port 3000)
- Starts storage service (port 8080)
2. Test Complete Asset Lifecycle:
Step 1: Upload File to IPFS
curl -X POST http://localhost:8080/upload \
-F "file=@test-asset.png"
# Save the returned CIDStep 2: Register Asset on Blockchain
curl -X POST http://localhost:3000/api/assets \
-H "Content-Type: application/json" \
-d '{
"name": "Test Asset",
"description": "Integration test asset",
"metadataCID": "bafybeig...",
"assetHash": "0x1234..."
}'
# Save the returned asset IDStep 3: Verify Asset
curl -X POST http://localhost:3000/api/assets/1/verify \
-H "Content-Type: application/json" \
-d '{"publicKey": "0x..."}'Step 4: Assign Ownership
curl -X POST http://localhost:3000/api/assets/1/claim \
-H "Content-Type: application/json" \
-d '{"ownerPublicKey": "0x..."}'Step 5: Transfer Ownership
curl -X POST http://localhost:3000/api/assets/1/transfer \
-H "Content-Type: application/json" \
-d '{"newOwnerPublicKey": "0x..."}'Step 6: Verify New Ownership
curl -X POST http://localhost:3000/api/assets/1/verify \
-H "Content-Type: application/json" \
-d '{"publicKey": "0x...(new owner)"}'1. Open Application:
Navigate to http://localhost:3000
2. Test User Flows:
- Create new asset
- View asset details
- Verify asset authenticity
- Transfer ownership
- View activity logs
- Check dashboard statistics
3. Test Error Handling:
- Try to verify non-existent asset
- Try to transfer asset you don't own
- Upload invalid file formats
- Submit invalid CIDs
Issue: "Contract not found"
# Ensure services are running
docker compose ps
# Restart services
bun run services:down
bun run services:up
# Recompile and redeploy
bun run compact:compile
bun run deployIssue: "DUST generation timeout"
# Wait longer (DUST generation can take 30-60 seconds)
# Or check node logs
docker compose logs nodeIssue: "Cannot find module '../managed/zera/contract'"
# Compile contracts first
bun run compact:compile
bun --filter @zera/contracts buildIssue: "Cannot connect to database"
# Check PostgreSQL is running
docker ps | grep postgres
# Verify DATABASE_URL in .env
cat web/.env | grep DATABASE_URL
# Reset database
cd web
bun run prisma:db-push --force-resetIssue: "Contract artifacts not found"
# Copy artifacts to web/public
bun run copy-artifacts
# Verify files exist
ls -la web/public/managed/zera/Issue: "Wallet sync failed"
# Check Midnight services are running
curl http://localhost:8088/health # Indexer
curl http://localhost:6300/version # Proof server
# Clear wallet state
rm -rf web/midnight-level-dbIssue: "PINATA_JWT not found"
# Check .env file exists
ls -la storage/.env
# Verify JWT is set
cat storage/.env | grep PINATA_JWT
# If missing, add your JWT token
echo "PINATA_JWT=your_token_here" > storage/.envIssue: "Upload failed with status 401"
# Invalid Pinata JWT token
# Generate new token at: https://app.pinata.cloud/keys
# Update storage/.env with new token
# Restart serviceIssue: "Port 8080 already in use"
# Find process using port
lsof -i :8080 # macOS/Linux
netstat -ano | findstr :8080 # Windows
# Kill process or change port
export PORT=8081
cargo run --releaseIssue: Services won't start
# Check Docker is running
docker ps
# View service logs
docker compose logs
# Restart Docker daemon
# macOS: Restart Docker Desktop
# Linux: sudo systemctl restart docker
# Clean and restart
docker compose down -v
docker compose up -d --waitIssue: "Unhealthy" service status
# Check specific service logs
docker compose logs indexer
docker compose logs node
# Wait longer for services to initialize
# Indexer can take 30-60 seconds to become healthy
# Restart specific service
docker compose restart indexerIssue: Tests are slow
# ZK proof generation is computationally intensive
# Expected test duration: 30-60 seconds
# Use release builds for better performance
cargo build --releaseIssue: Out of memory
# Increase Node.js memory limit
export NODE_OPTIONS='--max-old-space-size=4096'
# Or run with increased memory
NODE_OPTIONS='--max-old-space-size=4096' bun run testIssue: Clean slate needed
# Complete cleanup and restart
bun run services:down
bun run clean
docker system prune -a
bun install
bun run project:prepare
bun run testBefore considering local testing complete, verify:
- All contract tests pass
- Contract deploys successfully
- Asset registration works
- Ownership assignment works
- Ownership transfer works
- Asset verification works
- Error handling works correctly
- Application starts without errors
- Database connection works
- Contract artifacts loaded
- Asset creation flow works
- Asset listing works
- Asset details page works
- Verification flow works
- Transfer flow works
- Service starts successfully
- Health check responds
- File upload works
- CID verification works
- File fetch works
- Error handling works
- End-to-end asset lifecycle works
- All services communicate correctly
- Database updates reflect blockchain state
- IPFS files are accessible
- ZK proofs generate and verify
- Contracts README - Smart contract documentation
- Web README - Web application documentation
- Storage README - Storage service documentation
- Midnight Documentation - Midnight Network docs
- Vitest Documentation - Testing framework docs
For testing issues:
- Check service logs:
docker compose logs - Verify all prerequisites are installed
- Ensure environment variables are set correctly
- Review error messages carefully
- Try clean restart:
bun run services:down && bun run project:prepare