A platform that turns your GitHub contribution history into on-chain soulbound NFT credentials. Connect your GitHub account, index your repositories, earn achievements based on your activity, and mint non-transferable NFT credentials on Sepolia.
Live site:
Developer reputation today is fragile — it lives on centralized platforms, exposes raw activity data, and can't be independently verified. Skill Chain is a decentralized identity and achievement protocol that changes that.
- GitHub activity is verified on-chain — your contributions aren't just self-reported
- Skills are minted as soulbound NFTs — non-transferable credentials tied to your wallet
- Reputation scores live on-chain — portable across any platform that reads the chain
- Privacy-first — instead of exposing raw GitHub data, you prove what you've done without revealing everything behind it (FHE roadmap)
- Others can verify skills on-chain — no middleman, no platform lock-in
Privacy-first reputation for builders.
┌─────────────────────────────────┐
│ User Browser │
│ │
│ Next.js Frontend (Vercel) │
│ - RainbowKit / wagmi / viem │
│ - NextAuth (GitHub OAuth) │
│ - TanStack Query │
└──────┬──────────┬───────────────┘
│ │
REST API calls │ │ GraphQL queries
│ │
┌──────────▼──┐ ┌───▼────────────────┐
│ Backend │ │ The Graph │
│ (Railway) │ │ Subgraph │
│ │ │ │
│ Express │ │ Indexes on-chain │
│ ethers.js │ │ NFT mint/revoke │
│ Pinata │ │ proof events │
└──────┬──────┘ └────────────────────┘
│ ▲
DB reads │ │ Contract events
& writes │ │
┌──────▼──────┐ ┌─────────┴──────────┐
│ Supabase │ │ Sepolia Blockchain │
│ │ │ │
│ users │ │ ProofVerifier │
│ nonces │ │ SoulboundNft │
│ achievements│ │ │
│ user_ │ └────────────────────┘
│ achievements│ ▲
└─────────────┘ │
│ Contract calls
│ (wagmi/viem)
┌─────────┴──────────┐
│ User Browser │
│ (same frontend) │
└────────────────────┘
Authentication
- User must connect both GitHub (via NextAuth OAuth) and a wallet (via RainbowKit) before accessing the dashboard or minting
- The wallet address is linked to the GitHub session and synced via a cookie for SSR
GitHub Indexing
- On login the backend fetches the user's repos and commits via the GitHub GraphQL API
- Repos and commit counts are used to calculate a developer score
- If the user has at least 10 repositories they automatically earn their first achievement on login
Achievement Claiming
- Frontend calls
GET /api/nonceto get a one-time nonce from the backend - User signs a message containing their user ID, wallet address, and nonce with their wallet
- Frontend sends the signed message to
POST /api/sign-proof - Backend verifies the wallet signature, consumes the nonce, and returns a backend-signed proof
- Frontend submits the proof to the
ProofVerifiercontract on-chain using wagmi/viem
NFT Minting
- Frontend calls
POST /api/mintwith the user's stats and achievement ID - Backend uploads NFT metadata (name, description, image, attributes) to IPFS via Pinata
- Backend calls
SoulboundNft.issue()on-chain using the minter wallet - The minted token is non-transferable and tied to the user's wallet forever
On-chain Indexing
- The Graph subgraph listens to
IssuedandRevokedevents fromSoulboundNft - Frontend queries the subgraph via GraphQL to display the user's minted NFTs
Database (Supabase)
- Stores users, nonces, achievements, and user achievement records
- Backend is the only service that reads/writes to Supabase directly
- Frontend never touches Supabase — all DB access goes through the backend API
Achievements are unlocked based on your GitHub activity and minted as soulbound NFTs on-chain.
| Achievement | Criteria |
|---|---|
| First Login | GitHub Account connected |
| Has 10 Repos | Connect GitHub with at least 10 repositories |
More achievements are unlocked based on commit count, repo count, and developer score. Each achievement can only be claimed once per wallet and is non-transferable.
- Connect — Sign in with GitHub and connect your wallet
- Index — Your repositories and commits are fetched via the GitHub GraphQL API
- Earn — Achievements are unlocked based on your repo count, commit count, and score
- Claim — Sign a message with your wallet to request a backend-signed proof
- Mint — Submit the proof on-chain and receive a soulbound NFT credential
my-app/
├── packages/
│ ├── frontend/ # Next.js 15 app (deployed to Vercel)
│ ├── backend/ # Express API (deployed to Railway)
│ ├── contract/ # Solidity contracts (deployed to Sepolia)
│ ├── indexer/ # GitHub indexer + The Graph subgraph
│ └── shared/ # Shared types, ABIs, config, addresses
├── turbo.json
├── pnpm-workspace.yaml
└── vercel.json
| Package | Description | Docs |
|---|---|---|
@my-app/frontend |
Next.js frontend with RainbowKit, wagmi, NextAuth | README |
@my-app/backend |
Express API for proof signing, minting, and Supabase | README |
@my-app/contract |
ProofVerifier and SoulboundNft Solidity contracts |
README |
@my-app/indexer |
GitHub GraphQL indexer and The Graph subgraph | README |
@my-app/shared |
Shared TypeScript types, ABIs, addresses, config | README |
| Contract | Address |
|---|---|
ProofVerifier |
0xfa4ecd7e60a06567c5d16e7fcb05c46f479d0451 |
SoulboundNft |
0xb22f6d60c49f325813185c77b8a5b3b23eb130e6 |
- Frontend — Next.js 15, RainbowKit, wagmi, NextAuth, TanStack Query, Tailwind CSS v4
- Backend — Express, ethers.js, Supabase, Pinata (IPFS)
- Contracts — Solidity, Foundry, OpenZeppelin
- Indexer — The Graph, GitHub GraphQL API
- Monorepo — pnpm workspaces, Turborepo
- Node.js 18+
- pnpm 10+
- Foundry (for contract development)
pnpm installpackages/frontend/.env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=
GITHUB_ID=
GITHUB_SECRET=
NEXT_PUBLIC_BACKEND_URL=http://localhost:3002
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
NEXT_PUBLIC_GRAPH_URL=https://api.studio.thegraph.com/query/1749766/skill-chain/version/latestpackages/backend/.env
PORT=3002
NEXTAUTH_SECRET=
SIGNER_PRIVATE_KEY=
MINTER_PRIVATE_KEY=
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
SEPOLIA_RPC_URL=
PINATA_API_KEY=
PINATA_SECRET_KEY=
FRONTEND_URL=http://localhost:3000# Run frontend and backend together
pnpm dev
# Or run individually
pnpm --filter frontend dev
pnpm --filter @my-app/backend devpnpm buildTurborepo builds all packages in the correct order — shared first, then backend and frontend in parallel.
| Package | Platform | Trigger |
|---|---|---|
frontend |
Vercel | Push to main (watches packages/frontend/** and packages/shared/**) |
backend |
Railway | Push to main (watches packages/backend/** and packages/shared/**) |
contract |
Sepolia via Foundry | Manual (pnpm deploy:sepolia) |
indexer |
The Graph Studio | Manual (pnpm graph:deploy) |
See individual package READMEs for detailed deployment instructions.
Contributions are welcome! Here's how to get started.
git clone https://github.com/somtech123/skill-chain.git
cd skill-chain/my-apppnpm installCopy the example env vars from the Getting Started section above into:
packages/frontend/.env.localpackages/backend/.envpackages/contract/.env(for deployment)
git checkout -b feat/your-feature-name
# or
git checkout -b fix/your-bug-fixBranch naming conventions:
feat/— new featurefix/— bug fixchore/— maintenance, refactoringdocs/— documentation updates
Run the relevant package locally while developing:
pnpm --filter frontend dev # frontend on localhost:3000
pnpm --filter @my-app/backend dev # backend on localhost:3002pnpm build
pnpm lintgit add .
git commit -m "feat: describe your change"
git push origin feat/your-feature-nameCommit message conventions:
feat:— new featurefix:— bug fixchore:— maintenancedocs:— documentationrefactor:— code restructure without behavior change
Go to https://github.com/somtech123/skill-chain and open a PR against main. Describe what you changed and why.
# Add to frontend only
pnpm add <package> --filter frontend
# Add to backend only
pnpm add <package> --filter @my-app/backend
# Add a dev dependency
pnpm add -D <package> --filter frontend# Runtime dependency
pnpm add <package> -w
# Dev dependency (most common for root — turbo, typescript, etc.)
pnpm add -D <package> -wTo use @my-app/shared inside another package:
pnpm add @my-app/shared --filter @my-app/backend --workspaceOr manually add to the package's package.json and run pnpm install:
{
"dependencies": {
"@my-app/shared": "workspace:*"
}
}pnpm remove <package> --filter frontend# In a specific package
pnpm update <package> --filter frontend
# Across all packages
pnpm update <package> -rpnpm --filter frontend dev
pnpm --filter @my-app/backend build
pnpm --filter @my-app/contract testpnpm -r build
pnpm -r lintMIT
