Pull blocks. Master crypto. Own your knowledge.
PullChain is a gamified Web3 education platform built on Base. Players pull blocks from a Jenga-style tower, learn real crypto concepts through topic cards, pass quizzes, and mint soulbound NFT certificates on Base Mainnet as permanent, on-chain proof of knowledge.
Built live in public by @mojeebeth — founder of BlindspotLab.
- What It Is
- Core Mechanics
- Difficulty Tiers
- Certificate System
- Tech Stack
- Database Schema
- API Routes
- Pages
- Word System
- Quiz Generation
- Smart Contract
- Environment Variables
- Local Development
- Deployment
- Builder
Most crypto education is passive — articles, videos, threads. PullChain makes it active and consequential.
Each block in the Jenga tower is a real Web3 topic. Pull a block, read the definition and trench example, answer 4 questions correctly, and keep the tower standing. Fail too many and your stability hits zero. Stay alive long enough and you unlock a soulbound NFT certificate on Base — permanent, non-transferable proof that you actually know your stuff.
The full learning path:
Each tier unlocks the next. Elite Mode requires a minted Easy certificate to enter.
| Mechanic | Detail |
|---|---|
| Blocks per game | 18, randomly selected from 500+ pool each session |
| Per block | Topic card → 4-question MCQ quiz |
| Stability damage | 22% (0/4) → 14% (1/4) → 8% (2/4) → 3% (3/4) → 0% (4/4) |
| Dangerous block bonus | +5% damage if block is the last remaining in its row |
| Lives | 3 per session — lose one when you score 0/4 on a quiz |
| Session state | Persisted in DB — lives, stability, score, words seen |
| Randomization | Fisher-Yates shuffle on every game start and replay |
| Resume | Active sessions resumable from the Dashboard |
| Tier | Topics | Victory Threshold | Reward |
|---|---|---|---|
| Easy | Wallets, gas, NFTs, market basics | 10 blocks | Soulbound ERC-721 cert |
| Medium | DeFi, AMMs, liquidations, MEV, oracles | 14 blocks | Soulbound ERC-721 cert |
| Hard | ZK proofs, EVM internals, consensus research | 16 blocks | Soulbound ERC-721 cert |
| Elite | CT slang, degen culture, crypto vocabulary | 18 blocks | Leaderboard glory |
Elite Mode is gated — you must have minted your Easy certificate on Base to enter. The gate check is done server-side against the certificates table.
Certificates are soulbound ERC-721 tokens on Base Mainnet.
- One per wallet per difficulty — permanent and non-duplicable
- Transfers are blocked at the contract level — forever
- Each token stores the difficulty level and quiz score on-chain
- Minting flow: game victory →
/certificate/mint→ Privy wallet → Base transaction → DB record - The
hasCertificate(address, uint8)function is used as a gate for Elite Mode access - Contract:
0x5b9e85bbee0ba7abc3f69833bf97ea8799e0e3b3
| Tool | Usage |
|---|---|
| Next.js 15 | App Router, Turbopack, server components |
| TypeScript | Strict mode throughout |
| Framer Motion | Tower animations, stability wobble, overlay transitions, result screens |
| Tailwind CSS | Minimal utility usage for layout |
| Tool | Usage |
|---|---|
Privy (@privy-io/react-auth) |
Email login + embedded EVM wallet creation |
Privy JWKS (jose) |
Server-side JWT verification via JWKS endpoint |
| Wagmi + Viem | On-chain reads, writes, chain switching, contract simulation |
Privy is configured to auto-create embedded EVM wallets for all users on login — no MetaMask required. External wallets (MetaMask, Coinbase Wallet) are also supported.
| Tool | Usage |
|---|---|
| Base Mainnet | All on-chain activity |
| Soulbound ERC-721 | PullChainCert.sol — custom certificate contract |
| Coinbase Smart Wallet | Supported via Privy embedded wallet |
| Tool | Usage |
|---|---|
| Supabase | Hosted PostgreSQL |
| Prisma 7 | ORM, schema management, prisma.config.ts with defineConfig |
| Tool | Usage |
|---|---|
| Resend | Transactional welcome emails on new user signup |
model User {
id String @id @default(cuid())
privy_id String @unique
wallet String? @unique
email String?
username String @unique
avatar_seed String @default(cuid())
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}
model Session {
id String @id @default(cuid())
user_id String
difficulty Difficulty
lives Int @default(3)
stability Int @default(100)
score Int @default(0)
words_seen String[] @default([])
status SessionStatus @default(active)
started_at DateTime @default(now())
ended_at DateTime?
}
model QuizAttempt {
id String @id @default(cuid())
session_id String
topic_id String
answers Json
score Int
passed Boolean
time_taken Int
created_at DateTime @default(now())
}
model Progress {
id String @id @default(cuid())
user_id String
difficulty Difficulty
topic_id String
status TopicStatus @default(locked)
quiz_score Int?
attempts Int @default(0)
completed_at DateTime?
}
model Certificate {
id String @id @default(cuid())
user_id String
difficulty Difficulty
score Int
words_learned Int
tx_hash String?
nft_token_id Int?
issued_at DateTime @default(now())
minted_at DateTime?
}
model LeaderboardEntry {
id String @id @default(cuid())
user_id String
difficulty Difficulty
mode LeaderboardMode @default(learning)
score Int
time_taken Int?
created_at DateTime @default(now())
}Enums:
Difficulty:easy | medium | hardTopicStatus:locked | unlocked | learned | passedSessionStatus:active | completed | failedLeaderboardMode:learning | elite
| Route | Method | Auth | Purpose |
|---|---|---|---|
/api/auth/sync |
POST | Public | Create or update user on Privy login, trigger welcome email |
/api/session |
POST | Bearer | Create new session or resume active one |
/api/session/[id] |
PATCH | Bearer | Update session state after each block pull |
/api/session/[id]/attempt |
POST | Bearer | Save quiz attempt + upsert progress record |
/api/session/active |
GET | Bearer | Fetch current active session for dashboard resume banner |
/api/certificate/mint |
GET | Bearer | Fetch all certificates for authenticated user |
/api/certificate/mint |
POST | Bearer | Create certificate record and record on-chain mint |
/api/leaderboard |
GET | Public | Fetch leaderboard by difficulty and mode |
/api/leaderboard |
POST | Bearer | Save score + time after game completion |
/api/progress |
GET | Public | Fetch recent progress records for dashboard |
All Bearer routes verify the Privy JWT using JOSE JWKS verification against https://auth.privy.io/api/v1/apps/{appId}/jwks.json.
| Route | Description |
|---|---|
/ |
Landing page with product overview |
/login |
Privy authentication (email + wallet) |
/dashboard |
User stats, recent progress, resume banner, wallet pill, sign out |
/learn |
Difficulty selector with how-it-works breakdown |
/learn/[difficulty] |
Main Jenga game — tower, HUD, topic card, quiz, result screen |
/elite |
Elite Slang Jenga — gated by Easy certificate |
/certificate |
View earned certificates + Elite Mode gate card |
/certificate/mint |
Mint soulbound NFT on Base with copyable wallet address |
/leaderboard |
Global rankings filterable by difficulty and mode |
/about |
About the project |
/privacy |
Privacy policy |
/terms |
Terms of service |
/lib/words/ ├── easy.ts — 500+ crypto fundamentals ├── medium.ts — 500+ DeFi and protocol topics ├── hard.ts — 500+ advanced research topics ├── slang.ts — 150 crypto slang and CT culture terms └── types.ts — Word and SlangWord interfaces
Word interface:
interface Word {
id: string;
term: string;
category: string;
definition: string;
useCase: string;
trenchExample: string;
difficulty: Difficulty;
distractors: [string, string, string];
}SlangWord interface:
interface SlangWord {
id: string;
slang: string;
origin: string;
definition: string;
example: string;
vibe: 'bullish' | 'bearish' | 'neutral' | 'chaotic';
difficulty: 'elite';
distractors: [string, string, string];
}For Elite Mode, SlangWord is adapted to the Word shape via a slangToWord() mapper so all game components work without modification:
slang→termorigin→useCaseexample→trenchExample
Each block automatically generates 4 MCQ questions:
| # | Question Type | Prompt |
|---|---|---|
| 1 | Definition match | "What does [term] mean?" |
| 2 | Category identification | "Which category is [term] in?" |
| 3 | Use case scenario | "Which scenario shows [term] in action?" |
| 4 | Trench example | "Spot the term: [trenchExample]" |
- Distractors are pre-authored per word (3 per word) — not generated at runtime
- Each question shuffles options on render
- Correct index is recalculated after shuffle
- Passing score: 4/4 correct = 0% stability damage; 0/4 = 22% damage + lose a life
PullChainCert.sol — Soulbound ERC-721 on Base Mainnet
function mint(uint8 difficultyLevel, uint256 score) external
function hasCertificate(address holder, uint8 difficulty) external view returns (bool)
function tokenIdOf(address holder, uint8 difficulty) external view returns (uint256)| Property | Detail |
|---|---|
| Standard | ERC-721 (soulbound) |
| Network | Base Mainnet |
| Supply | Unlimited — one per wallet per difficulty |
| Transferability | Permanently blocked — reverts on transfer attempt |
| Address | 0x5b9e85bbee0ba7abc3f69833bf97ea8799e0e3b3 |
| Difficulty encoding | 0 = easy, 1 = medium, 2 = hard |
# Privy
NEXT_PUBLIC_PRIVY_APP_ID=pk_...
PRIVY_APP_SECRET=sk_...
# Database
DATABASE_URL=postgresql://...
# Email
RESEND_API_KEY=re_...
# Blockchain
NEXT_PUBLIC_CONTRACT_ADDRESS=0x5b9e85bbee0ba7abc3f69833bf97ea8799e0e3b3
NEXT_PUBLIC_BASE_RPC=https://mainnet.base.org# Clone the repo
git clone https://github.com/your-org/pullchain
cd pullchain
# Install dependencies
pnpm install
# Set up environment variables
cp .env.example .env.local
# Fill in all values in .env.local
# Push database schema to Supabase
DATABASE_URL="your_connection_string" npx prisma db push
# Generate Prisma client
npx prisma generate
# Start development server
pnpm devThe app will be running at http://localhost:3000.
PullChain is deployed on Vercel.
- Connect your GitHub repo to a new Vercel project
- Add all environment variables in the Vercel dashboard under Settings → Environment Variables
- The build command runs Prisma generation automatically:
{
"scripts": {
"build": "prisma generate && next build"
}
}- Deploy — Vercel handles the rest
Built live in public by Mojeeb Titilayo (mojeeb.eth) — AI Native Indie Developer, & Senior Vibe Coder and founder of BlindspotLab, an AI-native build studio.
Prompt engineering is a core stack component — all quiz questions, distractor quality, topic card copy, and word data is the result of deliberately engineered prompts. Not generic LLM output.
| X / Twitter | @mojeebeth |
| Portfolio | mojeeb.xyz |
| Studio | blindspotlab.xyz |
| Support | [email protected] |
| 🌐 Live App | pullchain.fun |
| 🐦 X | @PullChainFun |
| 🔗 Contract | Basescan |
| 📧 Support | [email protected] |
Pull blocks. Master crypto. Own your knowledge.