A blockchain-secured digital passport system for luxury watches. WatchVault allows watch owners to create immutable records of their timepieces, track service history, and maintain provenance through blockchain technology.
- 🔐 Blockchain-Secured Records - Immutable watch provenance on Ethereum
- 👤 Social Login - Google and Facebook OAuth integration
- 📸 Image Upload - Optional watch images (max 8MB)
- 📱 QR Code Access - Public digital passports via QR codes
- 📜 Event Timeline - Track service, authentication, and transfer events
- 🎨 Modern UI - Capital One-inspired design system
- Next.js 14 - React framework with App Router
- TypeScript - Type-safe development
- Tailwind CSS - Utility-first styling
- Axios - HTTP client
- React OAuth - Google/Facebook authentication
- Node.js - Runtime environment
- Express - Web framework
- TypeScript - Type-safe development
- Prisma - ORM with SQLite database
- Multer - File upload handling
- JWT - Authentication tokens
- Hardhat - Ethereum development environment
- Solidity - Smart contract language
- Ethers.js - Blockchain interaction
- Node.js 18+ and npm
- Git
- Google Cloud Console account (for Google OAuth)
- Facebook Developer account (for Facebook OAuth)
git clone https://github.com/trietlu/WatchVault.git
cd WatchVaultcd backend
# Install dependencies
npm install
# Set up environment variables
cp .env.example .env
# Edit .env and add your configuration
# Initialize database
npx prisma generate
npx prisma migrate dev
# Start development server
npm run devThe backend will run on http://localhost:3001
The application uses SQLite with Prisma ORM. The database schema is already defined in backend/prisma/schema.prisma.
The schema includes four main models:
- User - User accounts with email/password and OAuth support
- Watch - Watch records with brand, model, and serial number hash
- WatchEvent - Timeline events (MINT, SERVICE, TRANSFER, AUTH)
- FileRecord - Uploaded watch images
cd backend
# Generate Prisma Client (creates TypeScript types)
npx prisma generate
# Create the database and run migrations
npx prisma migrate dev --name init
# (Optional) View database in Prisma Studio
npx prisma studionpx prisma generate- Generates TypeScript client from schemanpx prisma migrate dev- Creates migration and applies to databasenpx prisma studio- Opens GUI to view/edit database (http://localhost:5555)npx prisma db push- Pushes schema changes without creating migration (for prototyping)
The SQLite database is created at:
backend/prisma/dev.db
This file is excluded from Git via .gitignore.
If you need to start fresh:
cd backend
# Delete the database file
rm prisma/dev.db
# Recreate and migrate
npx prisma migrate dev --name initThe complete schema is in backend/prisma/schema.prisma:
model User {
id Int @id @default(autoincrement())
email String @unique
passwordHash String?
googleId String? @unique
facebookId String? @unique
createdAt DateTime @default(now())
watches Watch[]
}
model Watch {
id Int @id @default(autoincrement())
ownerId Int
owner User @relation(fields: [ownerId], references: [id])
serialNumberHash String @unique
brand String
model String
publicId String @unique @default(uuid())
qrCodeUrl String?
createdAt DateTime @default(now())
events WatchEvent[]
files FileRecord[]
}
model WatchEvent {
id Int @id @default(autoincrement())
watchId Int
watch Watch @relation(fields: [watchId], references: [id])
eventType String
payloadJson String
payloadHash String
txHash String?
blockNumber Int?
timestamp DateTime @default(now())
files FileRecord[]
}
model FileRecord {
id Int @id @default(autoincrement())
watchId Int?
watch Watch? @relation(fields: [watchId], references: [id])
eventId Int?
event WatchEvent? @relation(fields: [eventId], references: [id])
url String
type String
createdAt DateTime @default(now())
}cd frontend
# Install dependencies
npm install
# Set up environment variables
cp .env.local.example .env.local
# Edit .env.local and add your OAuth credentials:
# NEXT_PUBLIC_GOOGLE_CLIENT_ID=your_google_client_id
# NEXT_PUBLIC_FACEBOOK_APP_ID=your_facebook_app_id
# Start development server
npm run devThe frontend will run on http://localhost:3000
cd contracts
# Install dependencies
npm install
# Compile contracts
npx hardhat compile
# Deploy to local network
npx hardhat node
# In another terminal:
npx hardhat run scripts/deploy.js --network localhost- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
http://localhost:3000 - Copy Client ID to
frontend/.env.local
- Go to Facebook Developers
- Create a new app
- Add Facebook Login product
- Configure OAuth redirect URIs:
http://localhost:3000 - Copy App ID to
frontend/.env.local
Start all services in separate terminals:
# Terminal 1 - Backend
cd backend
npm run dev
# Terminal 2 - Frontend
cd frontend
npm run dev
# Terminal 3 - Blockchain (optional)
cd contracts
npx hardhat nodeAccess the application at http://localhost:3000
# Backend
cd backend
npm run build
npm start
# Frontend
cd frontend
npm run build
npm startWatchVault/
├── backend/ # Node.js/Express API
│ ├── prisma/ # Database schema and migrations
│ ├── src/
│ │ ├── controllers/ # Request handlers
│ │ ├── routes/ # API routes
│ │ ├── middleware/ # Auth, upload, etc.
│ │ └── config/ # Configuration files
│ └── uploads/ # User-uploaded images
├── frontend/ # Next.js application
│ ├── src/
│ │ ├── app/ # App router pages
│ │ ├── components/ # React components
│ │ └── lib/ # Utilities and API client
│ └── public/ # Static assets
└── contracts/ # Ethereum smart contracts
├── contracts/ # Solidity files
└── scripts/ # Deployment scripts
POST /auth/register- Register new userPOST /auth/login- Email/password loginPOST /auth/google- Google OAuth loginPOST /auth/facebook- Facebook OAuth login
POST /watches- Create new watch (with optional image)GET /watches- Get user's watchesGET /watches/:id- Get watch detailsPOST /watches/:id/images- Upload watch imageDELETE /watches/:id/images/:fileId- Delete watch imagePOST /watches/:id/events- Add event to watch
GET /passports/:publicId- Get public watch passport
Currently, the application uses manual testing. To test:
-
Registration/Login
- Create account at
/register - Login with email/password or OAuth
- Create account at
-
Watch Creation
- Navigate to "Add Watch"
- Fill in brand, model, serial number
- Optionally upload an image
- Submit to create watch
-
Image Management
- View watch detail page
- Upload image if none exists
- Delete existing image
-
Event Timeline
- Add events to watch
- View event history
- Check blockchain anchoring status
PORT=3001
JWT_SECRET=your_jwt_secret
DATABASE_URL="file:./dev.db"
NEXT_PUBLIC_GOOGLE_CLIENT_ID=your_google_client_id
NEXT_PUBLIC_FACEBOOK_APP_ID=your_facebook_app_id
- Check if port 3001 is available
- Ensure database is initialized:
npx prisma generate - Verify
.envfile exists with correct values
- Check if port 3000 is available
- Ensure
.env.localexists (OAuth keys are optional for basic functionality) - Clear
.nextfolder and rebuild:rm -rf .next && npm run dev
- Verify
backend/uploads/watchesdirectory exists - Check file size is under 8MB
- Ensure file type is JPEG, PNG, or WebP
- Verify OAuth credentials in
.env.local - Check redirect URIs in Google/Facebook console
- Ensure backend is running on port 3001
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request