Prestasi lo. Tidak bisa dipalsukan. Verifying authenticity, on-chain.
Sahih is an attestation-first credential layer for Indonesian higher education, built on the Solana Attestation Service (SAS). Universities, competition committees, and scholarship programs mint immutable, instantly verifiable student achievements directly to the Solana blockchain — and any verifier (HR, beasiswa, panitia kompetisi) can check authenticity in <1 second without contacting anyone.
The problem Sahih solves: a PDF certificate can be edited in 10 minutes in Photoshop. There is no fast, trustless way to verify Indonesian student achievements. The honest lose to the manipulators. Sahih moves the truth from PDF to chain — every attestation is signed by a registered authority, publicly verifiable, and impossible to fake.
Run locally (see Setup) and open:
| Surface | URL | What it does |
|---|---|---|
| Landing | / |
Marketing site, brand story |
| Issue console | /issue |
Mint form (issuers only) |
| Issue history | /issue/history |
Tab inside /issue — session-local audit |
| Verify | /verify |
Public verifier — paste any wallet, see all credentials |
| Portfolio | /portfolio/[address] |
Public student portfolio — every credential they own |
| Single credential | /credential/[pda] |
Public verify page for one attestation |
| OG card | /credential/[pda]/opengraph-image |
Auto-generated 1200×630 social preview |
| PDF certificate | /api/credential/[pda]/pdf |
Print-ready PDF + QR back to chain |
| Embeddable widget | /embed/[pda] |
One-line <iframe> for institution websites |
| Issuer registry | /issuers |
Public list of registered authorities |
| Network stats | /stats |
Aggregate signals from registry + protocol |
Everything is bilingual (/id/* + /en/*) — locale-detected on first visit,
toggle in the navbar/sidebar.
┌─ Browser ─────────────────────────────────────────────────────┐
│ Next.js 16 (App Router · Turbopack · React 19) │
│ next-intl · Tailwind v4 · Solana wallet adapter (Phantom) │
└─────────────────────────────┬─────────────────────────────────┘
│ POST /api/issue
↓
┌─ Server (Next.js Route Handler) ──────────────────────────────┐
│ isAuthorizedToIssue(wallet) ← lib/issuers.ts whitelist │
│ issueCredential(...) ← lib/sas.ts │
└─────────────────────────────┬─────────────────────────────────┘
│ signs + sends transaction
↓
┌─ Solana Devnet ───────────────────────────────────────────────┐
│ Solana Attestation Service │
│ - Sahih Credential PDA (authority root) │
│ - PRESTASI-V1 Schema (achievement_name, level, result, │
│ institution, date, category) │
│ - Per-student Attestation PDA │
└───────────────────────────────────────────────────────────────┘
Trust model. Currently the server holds a single ISSUER_KEYPAIR that
signs every mint. The connecting wallet (Phantom) is a soft auth gate
checked against lib/issuers.ts's registry. Production needs a signed
challenge from the user wallet — that's noted in the file header and is
the next architectural step.
- Next.js 16 with Turbopack and the App Router
- React 19 server components + client islands
- TypeScript strict mode end-to-end
- Tailwind CSS v4 with
@themedesign tokens (no config file needed) - next-intl 4 for
id(default) +enlocales with route-level prefixes - Solana via
@solana/web3.js,gill, andsas-lib - Wallet adapter — Phantom + Solflare (Mobile/desktop)
- @react-pdf/renderer for certificate generation
- qrcode for the certificate's verify-on-chain QR
- next/og for programmatic 1200×630 social preview cards
- sonner for toast notifications
Two-font lockup designed to feel like an attestation terminal, not a SaaS dashboard.
| Role | Font | Why |
|---|---|---|
| Display + body + labels + hashes | Space Mono | One typographic voice across the entire product — display at 96px and a wallet hash at 11px share the same font. OpenCode "everything is terminal" lineage |
| Accent (green moments) | Instrument Serif italic | The only break from mono — emotional pivot, always Solana Green, always italic, always halo'd |
Color palette. Pure black ground (#050505), Solana Green accent
(#14F195), white text fading through 0.65 / 0.40 / 0.22 alpha. No
secondary brand colors — every accent is the green.
Motion. CSS-only. Marquee on the landing ticker, fade-up on hero
entrance, pulse-green on the live status pill, hover-lift on credential
cards. Honors prefers-reduced-motion.
Full tokens + rules in DESIGN.md.
- Node.js ≥ 20
- npm (or pnpm / bun, all work)
- A Solana keypair with some Devnet SOL — used as the
ISSUER_KEYPAIR - A Helius RPC endpoint (free tier works) —
NEXT_PUBLIC_SOLANA_RPC_URL
npm installCreate .env.local:
# Required — Solana RPC endpoint
NEXT_PUBLIC_SOLANA_RPC_URL=https://devnet.helius-rpc.com/?api-key=YOUR_KEY
# Required — server-side issuer keypair (JSON byte array from `solana-keygen new`)
ISSUER_KEYPAIR=[12,34,56,...]
# Filled by `npm run setup:devnet` (see below)
NEXT_PUBLIC_CREDENTIAL_PDA=
NEXT_PUBLIC_SCHEMA_PDA=
# Optional — bypass issuer-authority gate for local dev
ISSUER_AUTH_DEV_BYPASS=1This creates the Sahih Credential authority and the PRESTASI-V1 schema on Devnet. Run once per environment:
npm run setup:devnetThe script prints the Credential PDA + Schema PDA — copy those into your
.env.local.
Mint 8 mock credentials across 4 demo wallets so the live URL has a
populated state for hackathon judges (no blank /verify page on first load):
npm run seed:demoAfter running, hit /verify?demo=1 or use the Try Demo CTA on the
hero — it auto-loads a populated wallet and renders all surfaces with
real on-chain data.
npm run dev # http://localhost:3000 → redirects to /id
npm run build # production build with route prerendering
npm start # production serverapp/
├── [locale]/ next-intl locale segment
│ ├── layout.tsx html shell + fonts + providers
│ ├── page.tsx landing
│ ├── credential/[pda]/ single credential public page
│ │ ├── page.tsx hero card + details + actions
│ │ ├── opengraph-image.tsx programmatic 1200×630 share card
│ │ ├── _share-button.tsx client island
│ │ └── _embed-snippet.tsx client island
│ ├── embed/[pda]/page.tsx iframe-optimized verify card
│ ├── issue/ mint form (sidebar dashboard)
│ │ ├── page.tsx Mint tab
│ │ └── history/page.tsx History tab (localStorage-backed)
│ ├── issuers/page.tsx public registry
│ ├── portfolio/ student credential portfolio
│ ├── stats/page.tsx aggregate metrics
│ ├── verify/page.tsx verifier surface
│ └── error.tsx error boundary
├── api/
│ ├── credential/[pda]/
│ │ ├── route.ts GET single credential JSON
│ │ └── pdf/route.ts PDF certificate + QR
│ ├── credentials/[address]/route.ts GET wallet portfolio
│ ├── issue/route.ts POST new attestation (auth-gated)
│ └── setup/route.ts dev-only protocol setup
├── globals.css @theme tokens + utilities
└── layout.tsx passthrough root
components/
├── app-shell.tsx dashboard sidebar (issue/verify/portfolio)
├── credential-card.tsx the credential pill
├── locale-switcher.tsx ID|EN toggle
├── navbar.tsx top nav (landing + public surfaces)
├── providers/solana-provider.tsx
└── wallet-button.tsx dynamic-imported wallet connect
i18n/
├── routing.ts locales + locale-aware Link/usePathname/useRouter
└── request.ts message loader
lib/
├── constants.ts schema + protocol constants
├── issuers.ts registered-issuer registry + auth gate
├── sas.ts Solana Attestation Service wrapper
├── types.ts shared types
└── utils.ts cn, shortenAddress, formatDate, isValidSolanaAddress
messages/
├── id.json Bahasa Indonesia (default)
└── en.json English
proxy.ts next-intl middleware (renamed from middleware.ts in Next 16)
scripts/setup-devnet.ts one-time on-chain setup
| Judge concern | Sahih's answer |
|---|---|
| "Cool, but does the on-chain part work?" | Yes — every credential is a real SAS attestation on Solana Devnet. Click any /credential/[pda] → "View on Solana Explorer" goes to the live PDA |
| "Can I share this with a non-crypto colleague?" | Click "Download Certificate (PDF)" — beautifully designed, scannable QR back to chain. Bridges Web2 expectations |
| "Can institutions integrate?" | One-line <iframe> embed shown right on the credential page, copy-paste ready |
| "How do you handle scale?" | SAS handles the writes; verification reads are public RPC, free, instant. Stats page shows the protocol-level numbers |
| "How do you prevent fake issuers?" | Server-side issuer-authority whitelist (lib/issuers.ts), public registry at /issuers, only registered wallets can mint |
| "Who is this for?" | Indonesian context first — id default locale, copy in Bahasa, partner stack lists Solana Foundation + Superteam ID + UGM |
- Add an issuer → append to
REGISTERED_ISSUERSinlib/issuers.ts - Add a credential category or level → edit
CREDENTIAL_CATEGORIES/CREDENTIAL_LEVELSinlib/constants.ts - Add a locale → add
messages/<code>.json, register ini18n/routing.ts'slocalesarray - Replace placeholder Freigeist with the real font → drop
.woff2files inpublic/fonts/freigeist/, swapOnestimport inapp/[locale]/layout.tsxfornext/font/local
MIT. Built for the Solana Indonesia ecosystem. See DESIGN.md for visual + brand details.