BlockDeal is a lightweight MVP for agreeing on a contract text with several participants and anchoring the final document hash on an EVM blockchain.
The product goal is simple:
- Create or open a shared contract room.
- Edit the agreement text together.
- Let participants mark their agreement.
- Lock the document when everyone has agreed.
- Compute a deterministic hash of the final text and participants.
- Send that hash to Sepolia as transaction data.
- Save the original text locally so the hash can be verified later.
This repository intentionally follows a KISS architecture: one React app, one Node server, one realtime layer, and a small room model.
Current state: MVP.
What works:
- Shared room links using hash routes like
#/room/<roomId>. - Realtime room sync with Socket.IO.
- Durable room state in SQLite.
- Editable contract text before final agreement.
- Participant list and agreement status.
- Document lock after all current participants agree.
- Hash calculation with
ethers.id(...). - MetaMask/browser wallet transaction with the hash in transaction
data. - Sepolia Etherscan link after anchoring.
- Download of the final source text as
.txt. - Basic Telegram WebApp user name detection when opened inside Telegram.
Known MVP limitations:
- The default database is SQLite, so production deploys need a persistent disk/volume.
- The architecture assumes one Node server instance. Move the repository adapter to PostgreSQL before running multiple instances.
- Current agreement status is a UI consent flag, not a cryptographic wallet signature.
- Text collaboration is last-write-wins, not Google Docs-style conflict-free editing.
- There is no user authentication or room access control yet.
- The blockchain transaction proves that a hash existed at a point in time; it does not prove legal signature by itself.
- React 19
- TypeScript
- Vite
- Express
- Socket.IO
- SQLite through
better-sqlite3 - ethers
- lucide-react
- Tailwind Vite plugin
- npm
BlockDeal uses the Trust Ledger design system for the app UI, landing pages, and promo materials.
- Source tokens live in
src/index.css. - Implementation guidance lives in
docs/trust-ledger-design-system.md. - The visual direction is calm fintech/legal-tech clarity, with
#00B47Eas the primary trust and confirmation color.
Current KISS recommendation:
- Keep React + Vite for the frontend.
- Keep Express + Socket.IO for the backend and realtime room events.
- Keep
ethersfor wallet and hash operations. - Keep SQLite as the default persistence layer while the app runs as one Node server.
- Keep dependencies tied to current product behavior; remove unused template packages instead of carrying them forward.
- Avoid Redux/Zustand until local React state becomes painful.
- Avoid microservices, monorepo setup, GraphQL, queues, or smart contracts until the product requires them.
- Move to PostgreSQL only when multiple server instances, managed backups, or higher write concurrency become real product needs.
The app has two main pieces:
- Frontend:
src/App.tsx - Backend:
server.ts
The frontend opens a room from the URL hash, connects to the Socket.IO server, and sends room events.
Example client events:
socket.emit('join_room', roomId, userName);
socket.emit('update_text', roomId, newText);
socket.emit('toggle_sign', roomId);
socket.emit('set_tx_hash', roomId, txHash);The server listens for these events, updates room state through the SQLite repository, and broadcasts the new room state to everyone in the same room.
Simple flow:
User edits text
-> browser sends update_text
-> server updates the room
-> server broadcasts text_updated
-> other room participants see the update
When all participants agree, the server marks the room as locked. The frontend then computes a hash from the final contract payload and can send that hash to Sepolia through the user's browser wallet.
BlockDeal currently uses Socket.IO for realtime sync.
This is a good MVP choice because it gives us:
- Named client/server events.
- Room-based broadcasting.
- Automatic reconnect behavior.
- WebSocket transport with fallback behavior.
- A simple API that works well with the current one-server architecture.
It is not SOTA for serious collaborative text editing. The current text sync is last-write-wins. If simultaneous editing becomes a core feature, the recommended upgrade path is Yjs for the document text while keeping Socket.IO for room events like presence, agreement status, and transaction hash updates.
More detail: docs/Socket_SOTA.MD
Current structure:
blockdeal/
docs/
Socket_SOTA.MD
architecture-kiss-proposal.md
src/
App.tsx
index.css
main.tsx
.env.example
.gitignore
index.html
metadata.json
package-lock.json
package.json
server.ts
data/
.gitkeep
tsconfig.json
vite.config.ts
Recommended next structure:
blockdeal/
docs/
src/
app/
App.tsx
components/
Header.tsx
ContractEditor.tsx
ParticipantsList.tsx
HashPanel.tsx
BottomActionBar.tsx
hooks/
useRoomRoute.ts
useTelegramUser.ts
useRoomSocket.ts
lib/
contractHash.ts
downloadText.ts
blockchain.ts
types/
room.ts
main.tsx
index.css
server/
index.ts
rooms.ts
storage/
db.ts
roomRepository.ts
schema.sql
sqliteRoomRepository.ts
socketHandlers.ts
types.ts
index.html
package.json
tsconfig.json
vite.config.ts
The goal is not to make the repo look bigger. The goal is to move separate responsibilities out of large files while keeping the app easy to understand.
Prerequisites:
- Node.js
- npm
- MetaMask or another browser wallet for blockchain anchoring
Install dependencies:
npm installCreate local environment file:
cp .env.example .env.localRun the app:
npm run devOpen:
http://localhost:3000
The app will create a room URL like:
http://localhost:3000/#/room/abc1234
Share that URL with another browser session to test realtime sync.
.env.example currently includes:
APP_URL="MY_APP_URL"
SQLITE_PATH="./data/blockdeal.sqlite"
APP_URL is useful for hosted environments where the app needs to know its public URL.
SQLITE_PATH controls where the server stores durable room state. The default path is ignored by git except for data/.gitkeep.
Before running BlockDeal with real users, add an automated SQLite backup job with remote object storage.
Recommended shape:
SQLite backup API
-> local timestamped .sqlite backup
-> gzip
-> S3-compatible object storage, for example Cloudflare R2, AWS S3, or Backblaze B2
Use the SQLite backup API exposed by better-sqlite3 instead of copying data/blockdeal.sqlite directly while the server is running. Direct file copies can miss WAL data.
Suggested retention:
hourly backups: keep 24 hours
daily backups: keep 14 days
weekly backups: keep 8 weeks
Required restore runbook:
1. Stop the Node server.
2. Download the selected remote backup.
3. Decompress it if needed.
4. Replace the current SQLITE_PATH file with the backup file.
5. Start the Node server.
6. Run npm run smoke and manually open one known room.
Production note: keep the live SQLite file on persistent storage and keep backups outside the application host. Volume snapshots are useful as an extra safety layer, but they should not be the only backup mechanism.
npm run devStarts the Express + Socket.IO server with Vite middleware on port 3000.
npm run buildBuilds the frontend with Vite.
npm run previewRuns Vite preview for the built frontend.
npm run lintRuns TypeScript checking with tsc --noEmit.
npm run typecheckAlias for TypeScript checking with tsc --noEmit.
npm run smokeRuns the minimum smoke suite for the MVP: TypeScript checking plus a production frontend build.
npm run cleanRemoves the dist directory.
BlockDeal does not currently deploy or call a smart contract.
The anchoring flow is:
- Build the final contract payload from text and participants.
- Compute
ethers.id(payload). - Ask the browser wallet for an account.
- Send a zero-value transaction to the user's own address.
- Put the document hash into the transaction
data. - Store and display the transaction hash in the room.
This records the hash on-chain without storing private contract text publicly.
Important: the original text file must be kept. The blockchain stores the hash, not the readable agreement.
The MVP trust model is intentionally minimal:
- Participants are identified by socket connection and display name.
- Agreement is a room state flag.
- The final hash commits to the room text and participant names/statuses.
- The on-chain transaction provides timestamped public evidence of the hash.
For stronger trust later, consider:
- Wallet-based identity.
signMessage(...)signatures from each participant.- Immutable agreement snapshots.
- Explicit room invitations or access tokens.
- Extract frontend types into
src/types/room.ts. - Extract hash logic into
src/lib/contractHash.ts. - Extract download logic into
src/lib/downloadText.ts. - Extract wallet transaction logic into
src/lib/blockchain.ts. - Split UI into small components.
- Move server room state operations into
server/rooms.ts. - Move Socket.IO event wiring into
server/socketHandlers.ts. - Add small unit tests for hash generation and room rules.
- Remove unused dependencies after confirming product scope.
- Move SQLite access behind additional service methods if room workflows become more complex.
Detailed proposal: docs/architecture-kiss-proposal.md
Potential upgrades, only when product needs are clear:
- Yjs for real simultaneous document editing.
- PostgreSQL when running multiple server instances or when managed database operations become necessary.
- Redis only when running multiple server instances.
- Wallet signatures for stronger agreement proof.
- Smart contract only if on-chain verification becomes a product requirement.
- Telegram-specific launch and sharing flow if Telegram becomes the main distribution channel.
This project originated from an AI Studio template, but the repo is now being shaped as a focused BlockDeal MVP.
Keep future changes small and behavior-oriented. Prefer extracting clear modules over adding new frameworks.
Unused AI Studio template dependencies have been removed from the active product setup. Add AI/Gemini packages back only when a concrete product flow needs them.
README.md is the source of truth for project rules, architecture direction, local workflow, and current product assumptions.
When making a meaningful code change, opening a PR, or adding a new feature, update this README in the same change if the behavior, setup, architecture, dependencies, roadmap, trust model, or developer workflow changes.
Before implementation work, contributors and agents should read the relevant README sections and keep changes aligned with the KISS direction described here.
Code comments should explain architecture boundaries, product rules, and non-obvious behavior. Avoid comments that merely restate the next line of code.
Every new feature must include automated tests and smoke coverage appropriate to its risk. For the current MVP, minimum smoke coverage is npm run smoke; when feature logic is extracted into pure modules, add focused unit tests for that logic.