CertChain là hệ thống cấp, quản trị và xác thực chứng chỉ bằng blockchain. Dữ liệu xác thực cuối cùng nằm trên smart contract, còn MongoDB dùng để phục vụ dashboard, tìm kiếm, QR và nghiệp vụ quản trị.

smart-contract: Hardhat + Solidity, contractCertRegistry.sollưu chứng chỉ on-chain.backend: Node.js + Express + MongoDB + ethers.js, xử lý admin API, IPFS, QR, JWT, đồng bộ dữ liệu và giao dịch ghi blockchain.frontend: Next.js + TypeScript, gồm trang xác thực công khai và dashboard quản trị.
Admin đăng nhập
→ Nhập thông tin chứng chỉ
→ Upload PDF
→ Backend-signed mode: backend hash PDF, upload IPFS và gửi issueCertificate()
→ MetaMask mode: frontend hash PDF, backend chỉ upload IPFS, MetaMask gửi issueCertificate()
→ MongoDB lưu metadata để dashboard tra cứu nhanh
→ Backend tạo QR trỏ tới /verify/{certId}Dữ liệu quan trọng được ghi on-chain:
certHashcertIdipfsCIDrecipientNamecourseNameissuingOrgissuerissuedAtisRevoked
Luồng xác thực công khai hiện tại đọc trực tiếp từ blockchain:
User nhập Cert ID hoặc upload PDF
→ Frontend dùng ethers.js gọi smart contract trực tiếp
→ Không cần backend cho bước verify
→ Hiển thị Valid / Revoked / Not FoundNếu xác thực bằng PDF:
Frontend tính SHA-256 bằng Web Crypto API
→ Convert thành bytes32
→ Gọi getCertificate(hash) trên smart contractNếu xác thực bằng mã chứng chỉ:
Frontend gọi getCertificateById(certId) trên smart contractQR chứa URL /verify/{certId}
→ Người dùng quét QR
→ Browser mở trang verify
→ Frontend đọc smart contract trực tiếp
→ Hiển thị kết quả xác thựcAdmin chọn chứng chỉ
→ Nhập lý do thu hồi
→ Backend gọi revokeCertificate()
→ Blockchain cập nhật isRevoked = true
→ MongoDB cập nhật trạng thái
→ Verify lại sẽ hiển thị Certificate RevokedĐây là cách chạy khuyến nghị. Docker tự chạy đủ service cần thiết:
- MongoDB
- Hardhat local blockchain
- Deploy/reuse smart contract local
- Backend API
- Frontend Next.js
- Docker Desktop hoặc Docker Engine đang chạy.
- Node.js 20+ nếu muốn chạy test/lint/build ngoài Docker.
- MetaMask nếu muốn test đăng nhập ví hoặc ký giao dịch bằng ví.
Kiểm tra Docker:
docker --version
docker compose versionChạy local Hardhat:
cd /Users/tolinh/Documents/Programming/blockchain/certchain
./scripts/dev-docker.shChạy Sepolia testnet bằng cùng entrypoint Docker:
cd /Users/tolinh/Documents/Programming/blockchain/certchain
./scripts/dev-docker.sh sepoliaScript sẽ tự lấy IP LAN của máy và in ra dạng:
CertChain Docker dev stack
- Frontend: http://192.168.x.x:3000
- Backend: http://192.168.x.x:5001/api
- Hardhat: http://192.168.x.x:8545Mở trên máy Mac:
http://localhost:3000Mở trên điện thoại cùng Wi-Fi:
http://192.168.x.x:3000Không dùng localhost trên điện thoại, vì localhost lúc đó là chính điện thoại chứ không phải máy Mac.
docker compose -f docker-compose.dev.yml psKết quả đúng: mongodb, hardhat, backend, frontend đều Up; hardhat và mongodb nên có trạng thái healthy.
Health backend:
curl -s http://localhost:5001/healthHealth Hardhat RPC:
curl -s -X POST http://localhost:8545 \
-H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'Hardhat local trả chain id:
{ "jsonrpc": "2.0", "id": 1, "result": "0x7a69" }Sau khi backend chạy xong:
curl -X POST http://localhost:5001/api/auth/seedTài khoản mặc định:
Username: admin
Password: Admin@123456Dừng service nhưng giữ database dev:
docker compose -f docker-compose.dev.yml downDừng và xóa sạch database dev:
docker compose -f docker-compose.dev.yml down -vChạy lại sau khi tắt máy:
cd /Users/tolinh/Documents/Programming/blockchain/certchain
./scripts/dev-docker.shscripts/dev-docker.sh tự động:
- Detect IP LAN của máy Mac.
- Set
FRONTEND_URL,PUBLIC_FRONTEND_URL,NEXT_PUBLIC_FRONTEND_URL,NEXT_PUBLIC_API_URL,NEXT_PUBLIC_RPC_URL,CORS_ALLOWED_ORIGINS. - QR xác thực dùng
PUBLIC_FRONTEND_URL/NEXT_PUBLIC_FRONTEND_URL; khi quét bằng điện thoại, giá trị này phải là IP LAN hoặc domain thật, không phảilocalhost. - Start MongoDB container.
- Start Hardhat local node.
- Chạy deploy contract local.
- Reuse contract local nếu
0x5FbDB2315678afecb367f032d93F642f64180aa3đã tồn tại, tránh lệch address. - Start backend và frontend.
Vì vậy khi chạy Docker dev:
- Không cần chạy MongoDB local.
- Không cần chạy
npx hardhat nodethủ công. - Không cần deploy contract thủ công.
- Không cần sửa
MONGODB_URItrongbackend/.envsang Docker host.
Chế độ này dùng MongoDB/backend/frontend trong Docker, nhưng giao dịch issueCertificate() được ghi lên Ethereum Sepolia thật qua Alchemy RPC. Không chạy Hardhat local.
Điểm khác nhau:
./scripts/dev-docker.shhoặc./scripts/dev-docker.sh local: lưu on-chain vào Hardhat local31337../scripts/dev-docker.sh sepolia: lưu on-chain vào Sepolia testnet11155111../scripts/dev-sepolia.shvẫn tồn tại như alias nội bộ cho chế độ Sepolia.
Cần có:
- Alchemy Sepolia RPC URL.
- Ví deploy/backend có Sepolia ETH testnet.
PRIVATE_KEYcủa ví deploy trongsmart-contract/.env.ADMIN_PRIVATE_KEYtrongbackend/.env. Ví này phải là admin của contract. Nếu dùng cùng ví deploy thì deployer mặc định đã là admin.- Không dùng private key Hardhat local
0xac0974...f80cho Sepolia. Key đó map tới0xf39F...2266và không có quyền trên contract Sepolia.
Ví dụ smart-contract/.env:
PRIVATE_KEY=0x_your_deployer_private_key
ALCHEMY_SEPOLIA_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
ETHERSCAN_API_KEY=your_etherscan_key_optionalVí dụ backend/.env:
PORT=5001
MONGODB_URI=mongodb://127.0.0.1:27017/certchain
JWT_SECRET=change_me
JWT_REFRESH_SECRET=change_me_refresh
PINATA_API_KEY=your_pinata_key
PINATA_SECRET_KEY=your_pinata_secret
PINATA_GATEWAY=https://gateway.pinata.cloud/ipfs
ALCHEMY_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
ADMIN_PRIVATE_KEY=0x_your_backend_or_deployer_private_key
CONTRACT_ADDRESS=0x_fill_after_deploy
FRONTEND_URL=http://localhost:3000
PUBLIC_FRONTEND_URL=http://localhost:3000
NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
BASE_URL=http://localhost:3000
DEFAULT_ADMIN_USERNAME=admin
DEFAULT_ADMIN_PASSWORD=Admin@123456Không commit private key thật.
cd /Users/tolinh/Documents/Programming/blockchain/certchain/smart-contract
npm ci
npm run deploy:sepoliaSau khi deploy, copy dòng:
Contract address: 0x...dán vào:
backend/.env:
CONTRACT_ADDRESS=0x_your_sepolia_contract_address
ALCHEMY_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEYfrontend/.env.local:
NEXT_PUBLIC_CONTRACT_ADDRESS=0x_your_sepolia_contract_address
NEXT_PUBLIC_CHAIN_ID=11155111
NEXT_PUBLIC_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
NEXT_PUBLIC_SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
NEXT_PUBLIC_ALCHEMY_KEY=YOUR_KEYNếu đang chạy stack Hardhat local thì dừng trước:
docker compose -f docker-compose.dev.yml downChạy Sepolia:
cd /Users/tolinh/Documents/Programming/blockchain/certchain
./scripts/dev-docker.sh sepoliaScript sẽ in:
CertChain Sepolia Docker stack
- Frontend: http://...
- Backend: http://.../api
- RPC: https://eth-sepolia.g.alchemy.com/v2/...
- Chain: Sepolia (11155111)
- Contract: 0x...Seed admin:
curl -X POST http://localhost:5001/api/auth/seedSau khi cấp chứng chỉ thành công, giao diện sẽ hiển thị:
Mã giao dịchSố blockVí cấp chứng chỉ
Mở transaction trên Sepolia Etherscan:
https://sepolia.etherscan.io/tx/YOUR_TX_HASHMở contract trên Sepolia Etherscan:
https://sepolia.etherscan.io/address/YOUR_CONTRACT_ADDRESSChế độ Cấp qua MetaMask gọi issueCertificate() trực tiếp bằng signer của ví đang kết nối. Trên Sepolia Etherscan, trường From phải đúng ví MetaMask đó. Sau khi receipt thành công, frontend gọi backend sync để lưu MongoDB và tạo QR cho dashboard.
Chế độ Cấp qua backend vẫn dùng ADMIN_PRIVATE_KEY của backend để gửi transaction. Dùng mode này khi cần ví server cấp chứng chỉ thay cho browser wallet.
Không commit file .env, .env.local hoặc private key thật.
Docker dev override các biến kết nối nội bộ sau:
MONGODB_URI=mongodb://mongodb:27017/certchain
ALCHEMY_URL=http://hardhat:8545
CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3Các biến vẫn nên có trong backend/.env:
PORT=5001
NODE_ENV=development
JWT_SECRET=change_me_local
JWT_EXPIRES_IN=8h
JWT_REFRESH_SECRET=change_me_refresh_local
JWT_REFRESH_EXPIRES_IN=7d
PINATA_API_KEY=your_pinata_key
PINATA_SECRET_KEY=your_pinata_secret
PINATA_GATEWAY=https://gateway.pinata.cloud/ipfs
ADMIN_PRIVATE_KEY=local_hardhat_account_private_key
FRONTEND_URL=http://localhost:3000
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
BASE_URL=http://localhost:5001
DEFAULT_ADMIN_USERNAME=admin
DEFAULT_ADMIN_PASSWORD=Admin@123456
CONTRACT_DEPLOY_BLOCK=0
AUDIT_LOOKBACK_BLOCKS=1000
AUDIT_LOG_BLOCK_WINDOW=10
AUDIT_SCAN_FROM_DEPLOY=false
AUDIT_ENABLE_CHAIN_EVENTS=falseADMIN_PRIVATE_KEY chỉ dùng private key local của Hardhat account khi dev. Không dùng ví thật.
AUDIT_ENABLE_CHAIN_EVENTS=false giúp trang nhật ký kiểm toán mượt hơn trên Sepolia free-tier; dữ liệu cấp/thu hồi vẫn lấy ổn định từ MongoDB. Chỉ bật true khi cần làm giàu nhật ký bằng log blockchain trực tiếp.
Máy Mac local:
NEXT_PUBLIC_API_URL=http://localhost:5001/api
NEXT_PUBLIC_CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
NEXT_PUBLIC_CHAIN_ID=31337
NEXT_PUBLIC_RPC_URL=http://localhost:8545
NEXT_PUBLIC_ALCHEMY_KEY=
NEXT_PUBLIC_IPFS_GATEWAY=https://gateway.pinata.cloud/ipfs
NEXT_PUBLIC_ISSUING_ORG=CertChainĐiện thoại cùng Wi-Fi:
NEXT_PUBLIC_API_URL=http://YOUR_MAC_IP:5001/api
NEXT_PUBLIC_CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
NEXT_PUBLIC_CHAIN_ID=31337
NEXT_PUBLIC_RPC_URL=http://YOUR_MAC_IP:8545
NEXT_PUBLIC_ALCHEMY_KEY=
NEXT_PUBLIC_IPFS_GATEWAY=https://gateway.pinata.cloud/ipfs
NEXT_PUBLIC_ISSUING_ORG=CertChainLấy IP LAN:
ipconfig getifaddr en0Khi chạy bằng ./scripts/dev-docker.sh, script đã tự inject IP LAN vào Docker container.
Chỉ dùng khi cần debug từng service riêng.
brew services start mongodb-community
mongosh --quiet --eval "db.adminCommand({ ping: 1 })" mongodb://127.0.0.1:27017/certchaincd smart-contract
npx hardhat nodecd smart-contract
npx hardhat run scripts/deploy.js --network localhostCopy contract address vào:
backend/.env:
ALCHEMY_URL=http://127.0.0.1:8545
CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
ADMIN_PRIVATE_KEY=local_hardhat_account_private_keyfrontend/.env.local:
NEXT_PUBLIC_CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
NEXT_PUBLIC_CHAIN_ID=31337
NEXT_PUBLIC_RPC_URL=http://localhost:8545cd backend
npm ci
npm run devSeed admin:
curl -X POST http://localhost:5001/api/auth/seedcd frontend
npm ci
npm run devBase URL dev:
http://localhost:5001/apiAuth:
POST /auth/seed
POST /auth/login
POST /auth/login-metamask
POST /auth/link-wallet
POST /auth/logout
GET /auth/meCertificates admin, yêu cầu JWT:
POST /certificates/issue
POST /certificates/sync-from-chain
GET /certificates
GET /certificates/stats
GET /certificates/audit
GET /certificates/:certId
GET /certificates/:certId/qr
PUT /certificates/:certHash/revokeVerify public API vẫn tồn tại để tương thích và lịch sử, nhưng giao diện xác thực chính đọc blockchain trực tiếp:
POST /verify/by-id
POST /verify/by-file
POST /verify/by-hash
GET /verify/:certId/historyHealth:
GET /health
GET /api/healthSmart contract:
cd smart-contract
npx hardhat compile
npx hardhat testBackend:
cd backend
npm testFrontend:
cd frontend
npm run type-check
npm run lint
npm test
npm run buildLưu ý: chạy lệnh trong đúng thư mục con. Nếu chạy npm ở /Users/tolinh sẽ báo không tìm thấy package.json.
Chạy dev:
./scripts/dev-docker.shChạy dev nhưng ghi dữ liệu on-chain lên Sepolia:
./scripts/dev-docker.sh sepoliaXem trạng thái:
docker compose -f docker-compose.dev.yml psXem trạng thái Sepolia stack:
docker compose -f docker-compose.sepolia.yml psXem log:
docker compose -f docker-compose.dev.yml logs -f backend frontend hardhat mongodbRestart frontend/backend sau khi sửa code:
docker compose -f docker-compose.dev.yml restart frontend backendRestart frontend/backend trong Sepolia stack:
docker compose -f docker-compose.sepolia.yml restart frontend backendRebuild sạch frontend/backend:
docker compose -f docker-compose.dev.yml up -d --build --force-recreate frontend backendReset toàn bộ dev data:
docker compose -f docker-compose.dev.yml down -v
./scripts/dev-docker.shfailed to connect to the docker APICách xử lý:
- Mở Docker Desktop hoặc Docker Engine.
- Chờ Docker ready.
- Chạy lại
./scripts/dev-docker.sh.
MongoDB connection failed: connect ECONNREFUSED 127.0.0.1:27017Nếu chạy Docker dev, không cần MongoDB local. Chạy:
./scripts/dev-docker.shNếu chạy thủ công, phải start MongoDB local trước.
JsonRpcProvider failed to detect network
connect ECONNREFUSED 127.0.0.1:8545Docker:
docker compose -f docker-compose.dev.yml ps
docker compose -f docker-compose.dev.yml logs -f hardhatThủ công:
cd smart-contract
npx hardhat nodeNguyên nhân thường gặp: URL/RPC/API đang là localhost.
Cách xử lý:
ipconfig getifaddr en0
./scripts/dev-docker.shMở bằng URL được script in ra, ví dụ:
http://192.168.0.193:3000A certificate with this file already existsNguyên nhân: PDF đó đã được cấp trước đó, hash SHA-256 trùng.
Cách xử lý:
- Dùng PDF khác.
- Hoặc reset dev data:
docker compose -f docker-compose.dev.yml down -v
./scripts/dev-docker.shHardhat local blockchain là môi trường tạm. Khi reset Hardhat, dữ liệu on-chain có thể mất hoặc lệch với MongoDB.
Nếu cần môi trường sạch:
docker compose -f docker-compose.dev.yml down -v
./scripts/dev-docker.shNetwork:
Network name: Hardhat Local
RPC URL: http://127.0.0.1:8545
Chain ID: 31337
Currency symbol: ETHImport account dev bằng private key từ output npx hardhat node hoặc tài khoản local Hardhat. Chỉ dùng cho local development.
Khi deploy lên Sepolia hoặc production, cần đổi các biến sau:
Frontend:
NEXT_PUBLIC_CHAIN_ID=11155111
NEXT_PUBLIC_CONTRACT_ADDRESS=your_deployed_contract_address
NEXT_PUBLIC_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/your_key
NEXT_PUBLIC_ALCHEMY_KEY=your_key
NEXT_PUBLIC_API_URL=https://your-backend-domain/apiBackend:
MONGODB_URI=mongodb+srv://...
ALCHEMY_URL=https://eth-sepolia.g.alchemy.com/v2/your_key
CONTRACT_ADDRESS=your_deployed_contract_address
ADMIN_PRIVATE_KEY=server_wallet_private_key
FRONTEND_URL=https://your-frontend-domain
CORS_ALLOWED_ORIGINS=https://your-frontend-domain
BASE_URL=https://your-frontend-domainKhông commit các giá trị thật lên Git.
Public User
|
v
Next.js Frontend
|
|-- Direct read: ethers.js → CertRegistry smart contract
|
Admin User
|
v
Next.js Admin UI
|
v
Express Backend
| | |
v v v
MongoDB IPFS CertRegistry smart contract
|
v
Hardhat local / Ethereum Sepolia