See a small blog about this dApp.
TaskBoard is a dApp for creating and solving CTF-like tasks using smart contracts, IPFS, and zero-knowledge proofs (ZKP). Users solve challenges by proving knowledge of a hidden secret without revealing it. Successful solvers are rewarded with task-specific tokens.
To ensure trustless and scalable operation:
- Solutions are verified automatically (no manual approval).
- Tasks and descriptions are stored off-chain via IPFS.
- Proof validation is handled using ZK circuits.
- Submissions remain private to avoid copying.
- Rewards issued as non-transferable ERC-1155 tokens.
- Frontend – React
- Backend – Node.js (Fastify) + Postgres
- Smart contracts – Solidity (ERC-1155, AccessControl)
- Storage – IPFS (local - Kubo IPFS / Pinata)
- Proof system – snarkjs + Groth16
- Task creation – Creator commits a secret (Poseidon hash + salt) stored on IPFS.
- Solving – Solver submits a ZKP proving they know the secret.
- Verification – Smart contract checks the proof and mints a reward token.
- Compile the circuit and generate a trusted setup with
make circuit(testing only). - Export smart contract ABI with
make export-task-manager-abi. - Start the Postgres DB and use
make delete-dbandmake init-db.
- Postgres has to have user
appand created database calledtask_board_db.
- Start Anvil and deploy smart contracts with
make deploy-localand add creatoradd-creator-local.
- The creator is Anvil's account (1) with address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, add it to your wallet (e.g., MetaMask).
- Start Kubo IPFS
ipfs daemon. - Start the backend from
/backenddirectory withnpm run dev. - Start the frontend from
/frontenddirectory withnpm run dev.
### ENV for local Anvil node
PRIVATE_KEY_LOCAL=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
PRIVATE_KEY_CREATOR=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
PRIVATE_KEY_SOLVER=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
CREATOR_ADDR=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
TASK_MANAGER_ADDR=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
TASK_ACCESS_ADDR=0x5FbDB2315678afecb367f032d93F642f64180aa3
TASK_ID_TO_DEACTIVATE=1
### ENV for Sepolia deployment
PRIVATE_KEY=0xYOUR_PRIVATE_KEY # Wallet private key
ETHERSCAN_API_KEY=YOUR_ETHERSCAN_KEY # Etherscan API key (for verification)
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/<YOUR_ALCHEMY_KEY> # RPC provider (Alchemy)SERVER_PORT=3000
IPFS_STORAGE_MODE='local' # 'dev', 'local', or 'prod'
IPFS_LOCAL_API_URL='http://127.0.0.1:5001' # if IPFS_STORAGE_MODE='local', then this is the API URL of the local IPFS node
IPFS_LOCAL_GATEWAY_URL='http://127.0.0.1:8080' # if IPFS_STORAGE_MODE='local', then this is the gateway URL of the local IPFS node
PINATA_API_KEY='<Pinata API key>' # if IPFS_STORAGE_MODE='prod', then this is the Pinata API key
PINATA_JWT_TOKEN='<Pinata JWT token>' # if IPFS_STORAGE_MODE='prod', then this is the Pinata JWT token
PINATA_GATEWAY_URL='blush-peculiar-quail-924.mypinata.cloud' # if IPFS_STORAGE_MODE='prod', then this is the Pinata gateway URL
PINATA_TASKS_GROUP_ID='<Pinata tasks group ID>' # if IPFS_STORAGE_MODE='prod', then this is the Pinata tasks group ID
PINATA_IMAGES_GROUP_ID='<Pinata images group ID>' # if IPFS_STORAGE_MODE='prod', then this is the Pinata images group ID
LISTENER_MODE= 'local' # 'local' or 'prod'
INFURA_API_KEY='<Infura API Key>' # if LISTENER_MODE='prod', then this is the Infura API Key
CONTRACT_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 # TaskManager contract deployed address
START_BLOCK=0 # Needed for fetching history of events. This has to be number of block inside which the TaskManager contract was deployed
FINALITY_BLOCKS=5 # Number of blocks until events will be recorded into the database
DATABASE_USER='app'
DATABASE_PWD='app'
DATABASE_HOST='localhost'
DATABASE_NAME='task_board_db'
DATABASE_PORT=5432{
"name": "Stego Sleuth",
"description": "Proof of your ability to uncover secrets hidden in plain sight.",
"image": "ipfs://<cid>",
"task": {
"title": "Steganography in the Image",
"description": "A PNG file hides a message in its least significant bits. Extract it to find the secret number.",
"difficulty": "Easy",
"tags": ["stego", "forensics", "binary"],
"createdBy": "0x1234abcd...",
"createdAt": 1729849200,
"requirements": ["steganography tools", "hex editor"],
"resources": ["ipfs://<cid>"]
}
}