Inspiration
Indonesia has over 277 million citizens, each holding a physical KTP (Kartu Tanda Penduduk) as their sole proof of identity. Yet every year, millions of people face bureaucratic delays, counterfeit ID fraud, and inaccessibility — particularly those in remote areas or abroad. The NIK (Nomor Induk Kependudukan) system is entirely centralized: one database breach can compromise an entire nation's identity records.
We were inspired by the question: what if your identity was something no one could take away, forge, or manipulate — not even the government? Decentralized Identifiers (DIDs) on a public blockchain offer exactly that. Pair it with IPFS for tamper-proof credential storage and you have the foundation for a truly sovereign digital identity.
What it does
SatuIdentitas is a decentralized identity platform that replaces physical KTP/NIK documents with blockchain-anchored DIDs on the Ethereum Sepolia testnet.
For Indonesian Citizens (WNI):
- Submit NIK or KK number to generate a unique DID (
did:elpeef:citizen:<hash>) - DID is anchored on-chain via MetaMask — only
SHA-256(NIK)is stored, never the raw ID - A W3C Verifiable Credential is uploaded to IPFS via Pinata as a permanent certificate
- A Soulbound Token (SBT) card is generated as a non-transferable on-chain identity badge
For Tourists & Visa Holders (WNA):
- Register a temporary DID tied to passport and visa expiry date
- Real-time Overstay Detection monitors all active visas every 30 seconds and flags expired ones automatically
For Everyone:
- QR code generation in two modes: on-chain (Etherscan link) and offline (HMAC-SHA256 signed JWT)
- Batch identity verification API for institutions (banks, hospitals, employers)
- Fraud detection engine that flags duplicate NIK, suspicious patterns, and manual reports
- Offline JWT verification — works without internet by scanning a QR and verifying the signature locally
- SatuBot: an AI assistant powered by Google Gemini that answers questions about the platform in Bahasa Indonesia or English
How we built it
Frontend: React 18 + Vite, TanStack Query v5, Wouter, shadcn/ui, Framer Motion
Backend: Node.js + Express 5 + TypeScript, PostgreSQL via Drizzle ORM
Blockchain: Solidity smart contract (DIDRegistry.sol) deployed on Sepolia, integrated via ethers.js v6 and MetaMask
Storage: Pinata IPFS for W3C Verifiable Credential certificates (JSON-LD format)
AI: Google Gemini 2.0 Flash via @google/generative-ai for the SatuBot chatbot
Security: HMAC-SHA256 for offline JWT signing, SHA-256 hashing for NIK privacy on-chain
Monitoring: Background interval process running every 30 seconds to detect overstaying visa holders and emit structured event logs to the database
The entire stack runs as a single monorepo — shared TypeScript types between frontend and backend ensure end-to-end type safety from the database schema to the UI.
Challenges we ran into
Privacy vs. Transparency tradeoff on a public blockchain. Every transaction on Sepolia is publicly readable. We had to ensure the NIK never touches the chain — solving this by hashing on the client side before the MetaMask popup even appears, so the raw ID never leaves the user's device.
Immutability as a double-edged sword. The smart contract correctly prevents re-registration of an existing DID. But during development, recovering state after a failed registration (e.g., user rejected the MetaMask transaction mid-flow) required building an event-log recovery mechanism that scans DIDRegistered events on-chain to reconstruct the txHash without requiring re-registration.
Offline verification without a server. Making JWT QR codes verifiable without internet access meant encoding enough context into the token payload and providing a self-contained verification page — essentially a mini cryptographic verifier in the browser.
Real-time overstay detection at scale. Running a background monitor across all active visa holders required careful database indexing and ensuring the monitor wouldn't re-flag already-flagged identities on every cycle.
Routing conflicts with wildcard DID parameters. The /api/visa-identities/flagged route had to be registered before /api/did/:did in Express to avoid the wildcard consuming the static path — a subtle but critical ordering issue.
Accomplishments that we're proud of
- End-to-end on-chain identity flow — from form submission to MetaMask signature to IPFS certificate, entirely working on a live testnet
- Zero raw PII on the blockchain — SHA-256 hashing ensures citizen data cannot be scraped from the public ledger
- True offline verification — a QR code that can be verified cryptographically without any server or internet connection
- Real-time overstay monitoring — automatic detection and flagging of expired visa holders, with a live event feed UI
- Unified DID format — citizens and visitors share the same
did:elpeef:*namespace with consistent API behavior - Production-ready architecture — shared types, typed API routes, Zod validation on every endpoint, and a clean separation of concerns throughout
What we learned
- W3C DID and Verifiable Credential specifications are more accessible than they look — the core concepts map cleanly onto existing web infrastructure when you don't over-engineer the cryptography layer
- Blockchain is the right tool for immutability, not for storage — keeping heavy data on IPFS while anchoring only the minimal hash on-chain is the right pattern for identity systems at scale
- SHA-256 is not Zero Knowledge — it's one-way and computationally infeasible to reverse, but a true ZK proof (e.g., zk-SNARK) would allow proving identity attributes without revealing the hash at all; that's the natural next step
- UX around wallets is still the biggest barrier to Web3 adoption — MetaMask prompts, network switching, and gas fees are friction points that need more abstraction for mainstream users
- Real-time monitoring on a relational DB requires thoughtful state management — idempotency checks (
flaggedAt IS NULL) are essential to avoid duplicate events
What's next for SatuIdentitas
- Zero-Knowledge Proofs — upgrade the on-chain hash to a zk-SNARK proof so identity can be verified without revealing even the hash, achieving true cryptographic privacy
- Mainnet deployment — migrate from Sepolia testnet to Ethereum mainnet or a lower-cost L2 (Polygon, Optimism) for production viability
- DID-Auth login — allow third-party apps to authenticate users via their SatuIdentitas DID, replacing username/password with a cryptographic signature
- Mobile app with NFC — embed the offline JWT into an NFC-enabled card that can be tapped against a reader for instant physical verification
- Government integration API — a formal API layer for Dukcapil (civil registration office) and Imigrasi (immigration) to query and update DID status
- Selective disclosure — allow citizens to prove specific attributes (e.g., "I am over 18") without revealing their full identity, using Verifiable Presentations

Log in or sign up for Devpost to join the conversation.