A chat app whose whole backend is one Ethereum smart contract.
Sign in with MetaMask, add friends and chat; every account, friendship and message is a transaction that earns both sides tokens.
Live demo · Try it · How it works · Run locally · The contract
Last updated: 2026-10-16 · Live on Vercel (free), contract on the Hoodi testnet · a full rework is planned (see roadmap)
- About
- Try it live
- Features
- Why it exists
- How it works
- Engineering quality
- Run it locally
- Environment variables
- Deployment
- Status and roadmap
- Credits
Most chat apps keep your account and your messages on a company's servers. Node Connect keeps them in a smart contract instead: your wallet address is your account, a friendship is recorded on chain for both people, and each message is appended to the chat the two of you share. There is no server and no database; the website is static files that talk to the contract through MetaMask.
To make activity worth something, the contract pays tokens: 5 for creating an account, 1 to each side for a friendship and 1 to each side for every message. A leaderboard ranks everyone by tokens.
Open https://node-connect-sigma.vercel.app in a browser with the MetaMask extension. Every action is a transaction on the Hoodi testnet, so the account needs a little Hoodi ETH, which is free from a faucet such as the Google Cloud faucet or the PoW faucet.
MetaMask currently warns that the site and its contract are high risk. The live contract was deployed from a wallet whose key leaked, and wallet security services flag everything that wallet created. Redeploying from a fresh wallet is the first step of the rework (see Known limitations). To try the app without that warning, use the local chain; it needs no testnet ETH either.
Things to try in the first two minutes:
- Connect Wallet and approve the network switch: the app adds Hoodi to MetaMask if it is missing.
- Create an account with a username: the header balance shows 5 TOK once the transaction is mined.
- Open Add Friends and add someone: both of you get 1 TOK, and they appear in your friends list.
- Open the chat with that friend and send a message (Enter sends): both balances go up by 1.
- Open the Leaderboard to see everyone ranked by tokens.
![]() Registered users you can add, read from the contract (how friendships work) |
![]() Everyone ranked by tokens (the reward rules) |
Screenshots: the production build of this commit on a local Hardhat chain seeded with demo accounts (alice, bob, carol, dave, erin), 1440x900, 2026-10-16. The live site shows the same screens for Hoodi accounts.
- Wallet sign-in: MetaMask connects the account; the app switches MetaMask to the configured network and adds it when missing.
- On-chain accounts: one username per address, stored in the contract.
- Friends: add any registered user; the friendship is written for both sides.
- Chat: messages between friends are stored on chain in one shared chat per pair, oldest first, and the chat reloads when a sent message is mined.
- Token rewards: the contract pays 5 tokens per account, 1 to both sides per friendship and 1 to both sides per message; the header shows your balance.
- Leaderboard of every user ranked by tokens.
- Local chain mode: the same app runs against a Hardhat node with test accounts, no testnet ETH needed.
It started as a final-year project to learn how a dApp is built end to end: a Solidity contract, its tests, a deploy to a public testnet and a frontend that talks to it through a wallet. Bringing it up to date in 2026 (Next.js 16, ethers 6, Hoodi after Holesky was retired, CI) taught the rest: a leaked deployer key does not stop mattering when the file is deleted, and putting everything on chain makes every click cost a wallet confirmation and every message public.
flowchart LR
U["Browser + MetaMask"] --> V
subgraph Vercel["Vercel (free)"]
V["Static Next.js export<br/>no server code"]
end
U -- "reads (eth_call) and signed<br/>transactions, via ethers 6" --> R["Public Hoodi RPC node"]
R --> C[("ChatApp contract<br/>Hoodi testnet")]
What happens when you send a message:
sequenceDiagram
actor A as You
participant UI as Chat (ChatAppContext)
participant M as MetaMask
participant C as ChatApp contract
A->>UI: type a message, press Enter
UI->>M: sendMessage(friend, text)
M->>A: confirm the transaction and its gas
M->>C: signed transaction
C->>C: check you are friends, append to the shared chat, +1 token to both
C-->>UI: transaction mined
UI->>C: readMessage(friend), balances for the leaderboard
UI-->>A: chat and balance refreshed
The page holds no state of its own beyond what it read last: after each mined transaction,
Context/ChatAppContext.js reads the account, friends, chat and balances from the contract again.
contracts/ChatApp.sol is the whole backend.
| Call | Who can call it | Effect | Tokens |
|---|---|---|---|
createAccount(name) |
an address without an account; the name must not be empty | stores the username | +5 to you |
addFriend(address, name) |
a registered user, for another registered user who is not already a friend and not themselves | adds the friendship to both lists | +1 to both |
sendMessage(friend, text) |
friends only | appends to the chat keyed by the hash of both addresses | +1 to both |
readMessage(friend) |
anyone (eth_call) |
returns the chat, oldest first | - |
getAllAppUser(), getMyFriendList(), getUsername(address) |
anyone (eth_call) |
read accounts and friends | - |
mineMessageSafe(friend, nonce) |
anyone | +1 when keccak(chat, last message, nonce) is below a fixed target; not used by the UI |
+1 to the caller |
Tokens are a balance kept inside the contract (minerReward), not an ERC-20 token, so they do not
show up in a wallet.
| Layer | Technology |
|---|---|
| Smart contract | Solidity 0.8.19, Hardhat 2 (compile, local node, tests, deploy) |
| Frontend | Next.js 16 (static export), React 18, CSS Modules |
| Chain access | ethers 6 through MetaMask (BrowserProvider) |
| Network | Ethereum Hoodi testnet (chain 560048); a local Hardhat node (chain 1337) for development |
| Tests and checks | Hardhat tests (Mocha, Chai), ESLint (Next.js rules), an ABI freshness check |
| CI and hosting | GitHub Actions (CI on every push, daily uptime check), Vercel Hobby |
| Local setup | Node 22 pinned with mise |
- The contract is the whole backend. No server to run or pay for, and no one can edit the history. Trade-off: every write needs a wallet confirmation and gas, and everything stored is public, messages included.
- A static export on Vercel. The site is files only, so it is free to host and fast (Lighthouse performance 99 on desktop). Trade-off: there is no server to index data, so the leaderboard makes one call per user and sorts in the browser.
- Reads go through the user's wallet. One provider for reads and writes keeps the code small. Trade-off: without MetaMask the app cannot show any data.
- Configuration is checked in one place at build time.
Context/config.jsstops the build when the contract address or network is missing or malformed, so a bad deploy never goes live. - The contract ABI is a checked-in file, kept fresh by CI. The frontend imports
Context/ChatApp.json, and CI fails when it differs from the compiled contract.
Project structure
contracts/ChatApp.sol the smart contract (accounts, friends, messages, rewards)
test/ChatApp.test.js contract tests (Hardhat)
scripts/deploy.js deploys the contract; export-abi.js copies the ABI to Context/
Context/config.js reads and checks every environment variable (the only place that does)
Context/networks.js networks MetaMask can be switched to (hoodi, sepolia, localhost)
Context/ChatAppContext.js app state, read again from the contract after each mined transaction
Utils/apiFeature.js MetaMask connection and the contract instance
Components/ NavBar, Friend list and Chat, UserCard, Model (create account), Error
pages/ / (chat), /alluser (add friends), /leaderboard
.github/workflows/ ci.yml (lint, tests, ABI check, build), uptime.yml (daily)
| Area | What is in place |
|---|---|
| Tests | 9 Hardhat tests of the contract: accounts (reward, empty name, duplicates), friends (both sides, self, duplicate, unregistered), messages (shared chat order, non-friends rejected) and mining; all passing on 2026-10-16 |
| CI | GitHub Actions on every push: ESLint, the contract tests, a check that Context/ChatApp.json matches the compiled contract, and the production build |
| Uptime | A daily workflow requests /, /alluser and /leaderboard on the live site and fails unless each answers 200 with the app |
| Security | No secrets in the frontend (only public NEXT_PUBLIC_ values); the deployer key lives only in a local .env; security headers from vercel.json (nosniff, DENY framing, referrer and permissions policies) |
| Measured | Lighthouse on the live site, 2026-10-12 (mobile / desktop): performance 89-90 / 99, accessibility 100 / 100, best practices 100 / 100, SEO 100 / 100 |
| Dependencies | npm audit --omit=dev (what ships to the browser) reports no vulnerabilities; the remaining findings are in Hardhat's development toolchain |
Run them:
npm run lint # ESLint
npm test # contract tests
npm run build # static export to out/Works on Windows (Git Bash), macOS and Linux. You need Git, Node 22 (pinned in mise.toml;
mise installs it) and the MetaMask browser extension.
git clone https://github.com/A-RYAN-1/node-connect.git
cd node-connect
mise install # skip if you manage Node another way
npm ci
cp .env.example .env # the defaults point at the public Hoodi contract
npm run devOpen http://localhost:3000, connect MetaMask and approve the switch to Hoodi. Writes need a little
Hoodi ETH from a faucet (see Try it live).
On a local chain (no testnet ETH, no MetaMask warning):
npm run node # terminal 1: Hardhat node on http://127.0.0.1:8545 with funded test accounts
npm run deploy-local # terminal 2: deploys the contract and prints its addressIn .env, set NEXT_PUBLIC_CONTRACT_ADDRESS to the printed address and NEXT_PUBLIC_NETWORK=localhost,
run npm run dev, and import one of the test accounts npm run node prints into MetaMask. Those
accounts and keys are public and only for the local chain.
Deploy your own contract to Hoodi
- Create a fresh MetaMask account that will only ever hold testnet ETH, and fund it from a Hoodi faucet.
- Put its private key in
.envasDEPLOYER_PRIVATE_KEY(optionally an Alchemy or Infura URL asHOODI_RPC_URL). - Run
npm run deployand setNEXT_PUBLIC_CONTRACT_ADDRESSto the address it prints. - If you changed the contract, run
npm run abiso the frontend uses the new ABI.
Troubleshooting
- The build stops with a config error.
Context/config.jschecks the variables; copy.env.exampleto.env(or set them on the host) and build again. - MetaMask shows the wrong network. Click Connect Wallet again and approve the network switch.
- A transaction fails with "insufficient funds". The account needs a little Hoodi ETH.
- Local chain: MetaMask reports a nonce error after restarting
npm run node. The chain was reset; in MetaMask, Settings, Advanced, clear the activity data for that account. npm cifails on Node 18 or 20. The project needs Node 22 (mise install).
All of them are listed with comments in .env.example.
| Variable | What it is | Needed |
|---|---|---|
NEXT_PUBLIC_CONTRACT_ADDRESS |
Address of the deployed ChatApp contract. Public. |
app (build time) |
NEXT_PUBLIC_NETWORK |
Network MetaMask is switched to: hoodi, sepolia or localhost (Context/networks.js). Public. |
app (build time) |
HOODI_RPC_URL |
Hoodi RPC URL; defaults to the free public node | npm run deploy, optional |
DEPLOYER_PRIVATE_KEY |
Key of a testnet-only deployer wallet. Secret: only in your local .env, never on the host |
npm run deploy |
NEXT_PUBLIC_ values are compiled into the public JavaScript, so they must never hold a secret.
Never commit a real .env file; only .env.example is tracked.
The site is a static export on Vercel's free Hobby plan; the contract lives on the Hoodi testnet at
0x1C562FBbeA759113364Bf1EF282af19Bd8FD1A9f.
| Part | Where it runs | How it deploys | Variables it needs |
|---|---|---|---|
| Website | Vercel Hobby (free), https://node-connect-sigma.vercel.app | automatically on every push to main (Vercel Git integration; checked 2026-10-16) |
NEXT_PUBLIC_CONTRACT_ADDRESS, NEXT_PUBLIC_NETWORK (Production and Preview) |
| Contract | Hoodi testnet | by hand, npm run deploy from a local .env; never from CI |
DEPLOYER_PRIVATE_KEY, optional HOODI_RPC_URL (local only) |
What a visitor notices (checked 2026-10-16): the site itself has no cold start; every write waits for a Hoodi block and needs test ETH from a faucet; the public RPC node is shared and can be slow at times.
Host your own copy on Vercel
- Import the repository in Vercel (framework: Next.js, no root directory).
- Set
NEXT_PUBLIC_CONTRACT_ADDRESSandNEXT_PUBLIC_NETWORKin the project's Environment Variables for Production and Preview. Nothing secret is needed. - Deploy.
vercel.jsonadds the security headers;.vercelignorekeeps env files and build output out of CLI uploads.
Reached: production baseline on 2026-10-12 (Next.js 16 and ethers 6, contract tests, lint and build in CI, checked configuration, security headers, a daily uptime check, the Lighthouse scores above), and auto-deploy on push since 2026-10-16. Checked by hand on the live site on 2026-10-13: adding a friend and sending a message on Hoodi.
Known limitations
- MetaMask flags the site and the contract as high risk, because the contract was deployed from a wallet whose private key leaked (it was committed in 2025; the file is gone and the history was rewritten, but the key had already been taken). Only a redeploy from a fresh wallet fixes it.
- Messages are stored as plain text on a public chain: anyone can read any chat.
- Tokens are a balance inside the contract, not an ERC-20, and the rewards can be farmed with two
accounts of your own.
mineMessageSafecan be called again for the same message, so it is not safe to wire into the UI. - Adding a friend needs no consent from the other person, and there is no way to remove or block.
- Every action costs a MetaMask confirmation and gas, then a wait for the block.
- The leaderboard makes one call per user; lists such as
getAllAppUsergrow without a limit. - UI: the CHAT tab stays highlighted on every page, friend addresses are shown clipped without
0x, every friend gets the same avatar, and the GIFs inassets/weigh about 14 MB.
Planned: the rework (agreed 2026-10-13; each step merged and deployed before the next)
- Contracts and tests: an OpenZeppelin ERC-20 token (TOK) with a hard cap, a registry with friend requests (request, accept, decline, remove, block), deployed and verified from a fresh wallet, which should also end the MetaMask warning
- Backend on Vercel Functions and Neon: Sign-In with Ethereum, a messages API that stores only ciphertext, and a scheduled job that anchors each day's message hashes on chain
- End-to-end encrypted chats (keys derived from one wallet signature) and a new chat UI with pending and failed states
- Real token mining: proof of activity plus proof of work, one claim per user per day, bound to the sender, with retargeting and halving; mined in a Web Worker
- Production checks again (Slither in CI, API tests, Lighthouse), then this README
Built by Aryan Deshmukh (A-RYAN-1): the contract, its tests, the frontend, and the 2026 upgrade (Hoodi, Next.js 16, ethers 6, CI, deployment). RealFalseGod contributed the leaderboard page and the token balance in the header (October 2025).

