A lightweight demo/hackathon project that records a chain-of-custody on-chain (Ethereum / Hardhat) and anchors IoT sensor readings (signed hashes).
Includes: Solidity contract (CustodyRegistry), Node indexer + REST API (indexer.js) with SQLite, a demoFlow script to exercise the system, and a Next.js frontend with QR scanning and verification UI.
This README covers: what it is, architecture, exact files, prerequisites, full commands (Windows & *nix), .env example, how to run everything end-to-end, troubleshooting tips, and suggested next steps.
- Manufacturer registers a product batch on-chain.
- IoT devices sign sensor readings; hashes are anchored on-chain and raw payloads are stored in the indexer DB (for UI).
- Each handoff (transfer of custody) is a signed on-chain event.
- Consumer scans product QR → frontend calls indexer API → verifies chain-of-custody, sensor readings, and shows human-friendly labels and risk.
supplychain-hack/
├─ contracts/
│ └─ CustodyRegistry.sol
├─ scripts/
│ ├─ demoFlow.js
│ ├─ inspect_recent_txs.js
│ ├─ migrate_add_actors_and_payload_cols.js
│ ├─ seed_actors.js
│ └─ print_handoffs.js
├─ artifacts/ # Hardhat compiled output (ignored in git)
├─ indexer.js # Node/Express + sqlite indexer + /verify API + /storePayload endpoint
├─ supplychain.sqlite # local DB (ignored in git)
├─ package.json
├─ .env # local environment (ignored)
├─ frontend/ # Next.js app (can be part of monorepo or submodule)
│ └─ app/verify/page.tsx
└─ README.md
- Node.js v16+ (v18–22 works; in examples we used v22)
- npm (or yarn)
- Git
- Windows / macOS / Linux (commands below include both Windows and Bash examples)
NPM packages used (dev & runtime):
hardhat,@nomicfoundation/hardhat-toolbox(or minimal Hardhat + ethers)sqlite3express,cors,body-parser@zxing/browser(frontend QR scanning)node-fetch(demoFlow)- others in
package.json(runnpm install)
Create a .env at project root (DO NOT commit it). Example:
# local hardhat node RPC
RPC_URL=http://127.0.0.1:8545
# set to deployed contract address (or leave empty to have demoFlow deploy and print)
CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
# indexer
ARTIFACT_PATH=./artifacts/contracts/CustodyRegistry.sol/CustodyRegistry.json
DB_PATH=./supplychain.sqlite
START_BLOCK=0
PORT=4000Below are step-by-step commands and the reason for each. Use one terminal per long-running process.
Note: If you are on Windows PowerShell and
npxrefuses due to script restrictions, run the commands in CMD or enable script execution:Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser(careful, only if you understand the security implications). Alternatively runnpxfrom Git Bash.
From project root:
npm installIf you face ERESOLVE peer dependency errors when doing npx hardhat or create-next-app, you can use:
npm install --legacy-peer-deps(or force --force, but prefer --legacy-peer-deps).
npx hardhat compileThis produces artifacts/ and downloads compiler if needed. If you see pragma mismatch errors, ensure hardhat.config.js solidity field matches the pragma in CustodyRegistry.sol (example uses ^0.8.20).
npx hardhat nodeThis prints a set of accounts and private keys and starts a JSON-RPC server at http://127.0.0.1:8545. Keep this terminal open.
Option A (demoFlow will deploy a fresh registry if .env CONTRACT_ADDRESS is empty):
npx hardhat run scripts/demoFlow.js --network localhostOption B (deploy a contract then set .env CONTRACT_ADDRESS):
npx hardhat run scripts/deploy.js --network localhost
# (or use the Hardhat console to deploy manually)demoFlow.js also anchors sensor hashes and transfers custody — it’s handy for demos.
node indexer.jsWhat it does:
- Reads
ARTIFACT_PATHandCONTRACT_ADDRESSfrom.env. - Indexes past events (from
START_BLOCK) and subscribes to live events. - Exposes API:
GET /verify/:batchIdandPOST /storePayload(for raw payloads).
Important: start the indexer before you run demoFlow to make sure it receives live events. If you run demoFlow before indexer, you can either re-run demoFlow or index past events (indexer indexes past events on startup).
If CONTRACT_ADDRESS points to a deployed registry (or demoFlow deployed a fresh registry), run:
npx hardhat run scripts/demoFlow.js --network localhostdemoFlow does:
- registerBatch
- anchorSensor (2 readings, posts raw payload to indexer)
- transferCustody
It prints tx hashes and reading hashes and instructs you to curl the verify API.
Use the indexer API:
curl http://localhost:4000/verify/<BATCHID>Example (demoFlow batchId printed in script):
curl http://localhost:4000/verify/0x25bebd...This returns JSON:
{
"batch": {...},
"handoffs": [{id, batch_id, from_addr, to_addr, time}],
"sensors": [{id, reading_hash, signer, time, raw_payload, tempC, nonce}],
"risk": { score, reasons, label }
}Go to frontend:
cd frontend
npm install
npm run devThen open http://localhost:3000 and go to the verify page (e.g. /verify). Use the scan button or paste a batch id. The camera uses @zxing/browser — browser will ask for camera permission.
If QR camera keeps scanning in a loop: the frontend code sample included stops the camera on first result — ensure your page.tsx has the stop logic and you are not continuously re-calling startScanner().
scripts/demoFlow.js— demo flow for registering, anchoring, transferring.scripts/inspect_recent_txs.js— decodes recent txs to see logs.scripts/print_handoffs.js— prints thehandoffstable from sqlite.
Add this to project root (DO NOT commit .env, DB, artifacts):
node_modules/
artifacts/
cache/
typechain/
coverage/
.next/
out/
dist/
.env
.env.*
*.sqlite
*.sqlite-journal
logs/
*.log
.DS_Store
Thumbs.db
.vscode/
.idea/
If frontend was a separate Git repo and you want it included in the main repository, remove frontend/.git (or use Git submodule). See repository notes.
-
Cannot find module artifacts/.../CustodyRegistry.jsonRunnpx hardhat compilefrom project root. Also run scripts withnpx hardhat runif they expect Hardhat runtime. -
Error HH606 pragma statement don't matchMake surehardhat.config.jssolidity version matchespragmain contracts. Example:solidity: "0.8.20". -
npx hardhatfailing with peer/ERESOLVE while installing toolbox Usenpm install --legacy-peer-depsor install specific package versions (or create project without toolbox). -
PowerShell
npx.ps1 cannot be loadedRun commands in CMD/Git Bash or set execution policy:Set-ExecutionPolicy RemoteSigned -Scope CurrentUser(only if you understand security implications). -
Indexer shows
No custody transfers recordedEnsure indexer was running before demoFlow; otherwise restart indexer and re-run demoFlow. You can also deletesupplychain.sqliteto force reindex fromSTART_BLOCK=0then run indexer. -
Duplicate DB rows Add unique indexes and use
INSERT OR IGNOREinindexer.jshandlers. UsestorePayloadtoUPDATEexisting sensor rows rather than insert duplicates. -
Timestamp confusion (1970/incorrect) Raw payload
tsmay be milliseconds while chaintimeis seconds. Normalise: storepayload_tsas ms in DB and convert seconds → ms when showing:if (ts < 1e12) ts *= 1000. -
Frontend QR camera issues Make sure
@zxing/browserversion works with your React/Next version. Stop video tracks on scan:video.srcObject.getTracks().forEach(t => t.stop()). -
embedded git repository: frontendwarning whengit add .Remove nested.gitif you wantfrontendas part of repo:rm -rf frontend/.gitandgit rm -r --cached frontend; git add frontend; git commit -m "Include frontend subfolder".
-
GET /verify/:batchIdReturns: JSON withbatch,handoffs,sensors,risk. -
POST /storePayloadBody:{ batchId, readingHash, rawPayload }Indexer endpoint to attach raw payload JSON (from IoT device or demoFlow) to a previously anchored reading.
-
batch:{ batch_id, ipfs_cid, manufacturer, created_at } -
handoff:{ id, batch_id, from_addr, to_addr, time }(time = unix seconds) -
sensor:{ id, batch_id, reading_hash, signer, time, raw_payload, tempC, payload_ts, nonce }raw_payloadis JSON (stringified), may containtempC,ts(ms),nonce.
-
risk:{ score, reasons[], label }— simple heuristics (origin present, custody continuity, sensor thresholds).
In the frontend we display friendly names via actors table (seeded by scripts/seed_actors.js), short_hash, and parsed payload fields (temp, nonce, readable time).
- This is a hackathon/demo prototype — do not use this as-is in production. Signed sensor data MUST be verified on the server (recover signer from signature), private keys must be managed securely, and off-chain indexer trust model must be hardened.
- Do not commit
.envor private keys. - If you committed secrets by mistake, rotate keys and consider rewriting Git history (advanced).
- Verify and recover signer signature server-side — ensure
anchorSensorincludes signature bytes and validate ECDSA on indexer. - Add role management on-chain (operators, whitelists).
- Integrate IPFS for product metadata (store
ipfs_cidduring registerBatch). - Add simple anomaly detection (temperature spikes) to indexer to set risk reasons.
- Add on-chain registry of verified devices and manufacturer PKI.
PRs welcome — make a small change, open a PR, and include a short description of what you changed. For big changes open an issue first.
- MIT License (add
LICENSEfile if you want explicit license). - Built for HackAP 2025 / demo & learning purposes.
- For any other details