Skip to content

abdullahdevrangga11/Sahih

Repository files navigation

Sahih — Verified Student Credentials on Solana

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.


Demo

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.


Architecture

┌─ 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.


Tech stack

  • 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 @theme design tokens (no config file needed)
  • next-intl 4 for id (default) + en locales with route-level prefixes
  • Solana via @solana/web3.js, gill, and sas-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

Design system

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.


Setup

Prerequisites

  • 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

Install

npm install

Environment

Create .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=1

One-time on-chain setup

This creates the Sahih Credential authority and the PRESTASI-V1 schema on Devnet. Run once per environment:

npm run setup:devnet

The script prints the Credential PDA + Schema PDA — copy those into your .env.local.

Seed demo credentials

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:demo

After 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.

Run

npm run dev          # http://localhost:3000 → redirects to /id
npm run build        # production build with route prerendering
npm start            # production server

Project layout

app/
├── [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

Why we win

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

Contributing / extension points

  • Add an issuer → append to REGISTERED_ISSUERS in lib/issuers.ts
  • Add a credential category or level → edit CREDENTIAL_CATEGORIES / CREDENTIAL_LEVELS in lib/constants.ts
  • Add a locale → add messages/<code>.json, register in i18n/routing.ts's locales array
  • Replace placeholder Freigeist with the real font → drop .woff2 files in public/fonts/freigeist/, swap Onest import in app/[locale]/layout.tsx for next/font/local

License

MIT. Built for the Solana Indonesia ecosystem. See DESIGN.md for visual + brand details.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors