A full-stack Web3 real-estate demo that showcases:
- Fractional ownership of properties via ERC-1155
- On-chain governance (SELL / RESTRUCTURE / LOAN) with token-weighted voting
- Exit rounds where new buyers come in and existing holders cash out
- A backend indexer that listens to smart contract events over WebSockets and keeps a Postgres database in sync
- A Next.js dApp that talks directly to contracts for critical actions (buys, votes, proposals)
⚠️ Educational / demo only.
No real assets, no real investment products.
Do not use with real funds or deploy to mainnet.
-
Modular Solidity architecture on Sepolia:
PropertyToken1155(ERC-1155) for fractionalized properties:- Each
tokenId= one property - Primary sale via
buyShares - Tracks
maxSupply,totalSupply, andpricePerToken
- Each
PropertyGovernance:- Enum-based proposal types:
SELL,RESTRUCTURE,LOAN - Token-weighted voting and quorum / majority checks
executeProposalwired into valuation + exit logic
- Enum-based proposal types:
PropertyValuationRegistry:- On-chain reference price per token, updatable via governance
PropertyExitSale:- Exit rounds at a governance-approved price
- New buyers deposit ETH, old holders surrender tokens & claim ETH
-
Ownership & roles wiring:
- Governance owns the token, valuation registry and exit sale
- Only governance can set valuations and trigger exits
-
Hardhat workflows:
- Single deploy script that:
- Deploys all contracts
- Wires ownership / addresses
- Verifies contracts on Etherscan after deploy
- Single deploy script that:
-
Node.js + TypeScript + Express as a REST API for:
- Auth (JWT, email/password)
- Off-chain property & proposal metadata
- Portfolio / positions views
-
Prisma + PostgreSQL schema modeling:
User,Property,Investment,Proposal,Vote- Off-chain metadata for on-chain entities:
Proposal.onchainId,startBlock,endBlock,stateInvestment.txHash+logIndexwith uniqueness constraintProperty.referencePricePerTokenWei,referencePriceUpdatedAt
-
WebSocket-based indexer (via
viem):- Subscribes with
watchContractEventto:PropertyGovernance.ProposalCreatedPropertyGovernance.VoteCastPropertyGovernance.ProposalExecuted
- Uses
watchBlockNumberto track the current chain height - Keeps the DB in sync with:
- On-chain proposals (mapping metadataURI → DB rows)
- Vote weights & histories
- Proposal state transitions (ACTIVE → EXECUTED / DEFEATED)
- Subscribes with
-
Hybrid flow:
- Users now directly call
PropertyGovernance.createProposalfrom the frontend - The backend indexer listens to events and discovers / stores proposals coming from any wallet
- API reads from Postgres and can optionally enrich with on-chain
getProposalcalls
- Users now directly call
-
Next.js (App Router) + React + TypeScript
-
wagmi + viem for:
- Direct on-chain actions:
buySharesonPropertyToken1155createProposal,vote,executeProposalonPropertyGovernance- Exit flows on
PropertyExitSale:buyForExitclaimExitclaimExitTokens
- Reads via
usePublicClient:maxSupply,totalSupply,pricePerToken- Valuation data from
PropertyValuationRegistry - Exit state and per-user entitlements from
PropertyExitSale
- Direct on-chain actions:
-
UX that respects chain rules:
- "Can vote?" depends on:
- Wallet connected
- Proposal state =
ACTIVE - User ERC-1155 balance > 0 for that
tokenId
- "Can create SELL proposal?" depends on:
- Wallet connected
- Holder balance ≥ 20% of
maxSupply(mirrors the Solidity check)
- "Can execute?" depends on:
- Proposal state =
ACTIVE - Current block number (tracked via
watchBlockNumber) being greater thanendBlock
- Proposal state =
- "Can vote?" depends on:
-
Tailwind UI:
- Property detail page:
- On-chain stats cards
- Holdings summary
- Exit sale panel (progress, join, claim)
- Governance panel (proposals list + modal to create SELL proposals)
- Proposal detail page:
- Human-readable state & type mapping
- Vote counts
- Voting buttons with clear pending / error states
- Smart “Execute proposal” section that only enables the button once on-chain conditions are met
- Property detail page:
PropertyToken1155PropertyGovernancePropertyValuationRegistryPropertyExitSale
- Exposes JSON APIs under
/apifor:- Auth
- Properties
- Proposals
- Investments
- Portfolio / “me”
- Uses:
Prismafor all DB accessviemfor:- RPC reads (
readContract) - WebSocket subscriptions (
watchContractEvent,watchBlockNumber)
- RPC reads (
Rough structure:
-
app/page.tsx— Landingproperties/page.tsx— Properties listproperties/[id]/page.tsx— Property detail:- Invest, exit sale, holdings & governance section
proposals/[id]/page.tsx— Proposal detail, voting & executiondashboard/page.tsx— User portfolio viewadmin/properties/page.tsx— Admin panel for property approvals
-
abis/— TypeScript ABIs for all contracts -
lib/api.ts— Axios client configured with backend URL -
lib/wagmi.tsx— wagmi config for Sepolia + MetaMask etc.
- Admin approves property in the backend
- Backend calls the token contract to
createProperty(assignstokenId,maxSupply,pricePerToken) - Users invest via
buyShares(tokenId, amount)from the frontend
A large holder (≥ 20% of max supply for that tokenId) creates a SELL proposal:
- Frontend calls
PropertyGovernance.createProposal(tokenId, ProposalType.SELL, proposedPricePerTokenWei, metadataURI) metadataURIpoints back into the backend (JSON with title/description or simply the DB id)
Other holders vote YES/NO:
- Voting power = ERC-1155 balance at call time
executeProposal can be called by anyone once:
block.number > endBlock- Quorum is met
- YES ratio > configured threshold
If passed:
- Governance updates
PropertyValuationRegistrywith the reference price - Governance optionally calls
PropertyExitSale.openExit:- Sets exit price
- Sets
targetTokensfor the exit round
- New buyers call
buyForExitwith ETH at the exit price - Old holders:
- Approve the exit sale contract for their ERC-1155 tokens
- Call
claimExitto surrender tokens and receive ETH
- New buyers:
- Call
claimExitTokensafter enough tokens have been surrendered
- Call
The backend indexer subscribes over WebSockets to governance and exit events, and:
- Creates / updates
Proposalrows onProposalCreated - Creates
Voterows onVoteCast - Updates proposal state on
ProposalExecuted - Maps log index + tx hash to avoid duplicates
- Keeps off-chain views (e.g. proposal lists in the UI) stateless and fast
This is intentionally high-level; exact env vars / scripts live in each subfolder.
Inside contracts/:
- Configure
.envwith:SEPOLIA_RPC_URLPRIVATE_KEYETHERSCAN_API_KEY
Then:
npm install
npm run build
npm run deploy
The deploy script:
- Deploys
PropertyToken1155,PropertyValuationRegistry,PropertyGovernance,PropertyExitSale - Transfers ownership of token + valuation + exit sale to governance
- Links governance to exit sale
- Verifies contracts on Etherscan (Sepolia)
- Prints the addresses and a
.env-friendly summary
Copy those addresses into:
-
backend/.envGOVERNANCE_ADDRESS=0x...PROPERTY_TOKEN_ADDRESS=0x...PROPERTY_VALUTATION_REGISTRY_ADDRESS=0x...PROPERTY_EXIT_SALE_ADDRESS=0x...
-
frontend/.envNEXT_PUBLIC_GOVERNANCE_ADDRESS=0x...NEXT_PUBLIC_PROPERTY_TOKEN_ADDRESS=0x...NEXT_PUBLIC_PROPERTY_VALUTATION_ADDRESS=0x...NEXT_PUBLIC_PROPERTY_EXIT_SALE_ADDRESS=0x...
Inside backend/:
- Configure
.env:DATABASE_URLJWT_SECRET- RPC URL (for viem)
- Contract addresses (from deploy step)
Then:
npm install
npx prisma migrate dev
npm run dev
This will:
- Start the Express API
- Connect to Postgres
- Start the event indexer via WebSockets
Inside frontend/:
- Configure
.env:NEXT_PUBLIC_API_URL(pointing to the backend)NEXT_PUBLIC_*contract addresses
Then:
npm install
npm run dev
Open http://localhost:3000 and you can:
- Browse properties and invest
- Create SELL proposals (if you hold enough)
- Vote with your ERC-1155 balance
- Execute proposals once the voting period ends
- Participate in exit rounds as old or new holder
This is not financial advice and not production-ready code.
It’s a learning / portfolio project that demonstrates:
- Solidity contracts wired together into a coherent protocol
- A full-stack dApp with governance, exits, and indexed on-chain data
- Using WebSockets + viem to maintain an off-chain view in Postgres
- A Next.js / React UI with wallet integration and on-chain UX patterns
Use it to learn, fork, and experiment — not to handle real money.