Skip to content

Repository files navigation

PullChain

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.

Live Base Contract


Table of Contents


What It Is

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.


Core Mechanics

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

Difficulty Tiers

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.


Certificate System

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

Tech Stack

Frontend

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

Auth & Wallets

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.

Blockchain

Tool Usage
Base Mainnet All on-chain activity
Soulbound ERC-721 PullChainCert.sol — custom certificate contract
Coinbase Smart Wallet Supported via Privy embedded wallet

Database

Tool Usage
Supabase Hosted PostgreSQL
Prisma 7 ORM, schema management, prisma.config.ts with defineConfig

Email

Tool Usage
Resend Transactional welcome emails on new user signup

Database Schema

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 | hard
  • TopicStatus: locked | unlocked | learned | passed
  • SessionStatus: active | completed | failed
  • LeaderboardMode: learning | elite

API Routes

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.


Pages

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

Word System

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

  • slangterm
  • originuseCase
  • exampletrenchExample

Quiz Generation

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

Smart Contract

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

Environment Variables

# 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

Local Development

# 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 dev

The app will be running at http://localhost:3000.


Deployment

PullChain is deployed on Vercel.

  1. Connect your GitHub repo to a new Vercel project
  2. Add all environment variables in the Vercel dashboard under Settings → Environment Variables
  3. The build command runs Prisma generation automatically:
{
  "scripts": {
    "build": "prisma generate && next build"
  }
}
  1. Deploy — Vercel handles the rest

Builder

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]

Links

🌐 Live App pullchain.fun
🐦 X @PullChainFun
🔗 Contract Basescan
📧 Support [email protected]

Pull blocks. Master crypto. Own your knowledge.

About

Gamified Web3 education on Base — learn crypto concepts, mint soulbound certificates, climb the leaderboard.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages