MessChain
Prototype06.2026 - ∞|5m
A prepaid hostel-mess ledger on a private EVM chain, where a double charge is a contract revert rather than a reconciliation dispute.
The measurement
- Measured on
- counted from the repository: six contracts under hardhat/contracts, 27 route handlers across five Express routers
- Against a baseline of
- a paper register reconciled once a month, which anyone with access can edit
What it does
- Six Solidity contracts on a QBFT Hyperledger Besu network: Token, TimeLock, Rebate, MessChange, Governance and TenderRate. Every charge, meal claim and rebate is an append-only, independently verifiable record.
- TimeLock keys each day's charge on keccak256(student, mess, date), so a second charge for the same day reverts at the ledger, not in application code that a bug could route around.
- Business rules live in 28 require() guards: a rebate must start in the future and run at most 15 days, a mess change applies only after its effective date, a poll takes one vote per wallet.
- Complaint and feedback text is pinned to IPFS and only the CID is anchored on-chain, so the chain stays light and any edit to the text breaks its own hash.
- A midnight job bills every student through four on-chain checks - mess assigned, rebate inactive, balance sufficient, not already charged - which makes it safe to re-run on demand.
- Wired the React Native client to the live backend: QR meal verification with debounced scans, rebate requests pre-checked against the contract's reverts, and admin approvals that return real transaction hashes in place of mock receipts.
- Diagnosed why the chain had stopped producing blocks - the validator was signing with a key that did not match its registered identity - fixed it, and redeployed and verified the contracts.
How it is put together
Four figures: the shape, where the rules live, the job that runs them every night, and the chain underneath.
which component is allowed to talk to whicha dApp where every user holds a wallet and signs their own transactions
The blockchain is doing real work and is invisible to the people using it. Wallets are issued at registration and held by the API, so a student claims a meal by scanning a QR code, not by signing a transaction.
require() statements per contract, excluding the access-control modifiersthe same rules written only in the API, where any other code path could skip them
TimeLock is the one that carries the headline. It keys each day's charge on keccak256(student, mess, date) and refuses to write the same key twice, so double-billing is not a bug the code has to avoid; it is a transaction the chain will not accept.
Show the numbers
| Contract | Holds | Rule it enforces | Guards |
|---|---|---|---|
| Token | Meal-credit balances, minted by the admin | A deduction can never take a balance below zero | 3 |
| TimeLock | One record per student, mess and day | keccak256(student, mess, date) is written once; a second charge reverts | 5 |
| Rebate | Leave requests and their approvals | Starts in the future, ends after it starts, at most 15 days | 6 |
| MessChange | Transfer requests between messes | Applied only after its effective date, and only once | 9 |
| Governance | IPFS CIDs for complaints and feedback, and polls | One vote per wallet per poll, only while it is open | 4 |
| TenderRate | The daily meal rate over time | Every historical charge resolves to the rate in force that day | 1 |
the order of checks each student passes through before being billeda job that writes first and reconciles later, where a re-run double-charges
- 01
Mess assigned
Student has a mess in the roster; pending transfers are applied first
- 02
Rebate inactive
Rebate.isRebateActive(student, today) is false
- 03
Balance covers rate
Token balance >= TenderRate.getCurrentRate()
- 04
Not yet charged
TimeLock.hasTx(student, mess, today) is false
- 05
Charge
createDailyTx, then deductTokens. Re-running the job is a no-op
Any check that fails skips that student and logs why. Because the last check reads the chain's own record of today, triggering the job twice is a no-op, which is what made it safe to run on demand during the demo.
Express route handlers per routerthe 23 of them the Android client calls
Show the numbers
| Router | Routes | Covers |
|---|---|---|
| Admin | 14 | staff, minting, enrolment, approvals, roster, polls, cron trigger |
| Student | 8 | profile, balance, complaints, feedback, vote, rebate, transfer, meal claim |
| Auth | 2 | register, login |
| Staff | 2 | profile, QR verification |
| Public | 1 | the mess list |
Where it falls down
The network runs a single QBFT validator, so today it has the shape of Byzantine fault tolerance without the fault tolerance: one node is one point of trust. Wallets are custodial - the backend holds every key, encrypted with AES-256-GCM - which is what makes the app usable without a seed phrase, and also what makes the backend a trusted party. Team project: the contract and backend layer and the first client were built with a teammate; the integration, the infrastructure fix and the end-to-end verification are the parts claimed here.
Stack
- Solidity
- Hyperledger Besu
- QBFT
- Hardhat
- Ethers.js
- IPFS
- Node.js
- Prisma
- PostgreSQL
- React Native